@controleonline/ui-common 1.2.71 → 1.2.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/.github/actions/workers/manager/action.yml +147 -0
  2. package/.github/actions/workers/qa/action.yml +44 -0
  3. package/.github/actions/workers/security/action.yml +44 -0
  4. package/.github/actions/workers/technical-documenter/action.yml +51 -0
  5. package/.github/workflows/manager-worker.yml +168 -0
  6. package/.github/workflows/publish-npm.yml +45 -0
  7. package/.gitmodules +3 -0
  8. package/AGENTS.md +6 -0
  9. package/README.md +14 -0
  10. package/package.json +3 -2
  11. package/src/api/fetch.js +1 -1
  12. package/src/api/index.js +92 -22
  13. package/src/react/components/AddImportModal.js +94 -110
  14. package/src/react/components/BottomNavigationBar.config.js +211 -231
  15. package/src/react/components/CategoryForm.js +52 -35
  16. package/src/react/components/DefaultProvider.native.js +131 -95
  17. package/src/react/components/DefaultProvider.web.js +148 -74
  18. package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
  19. package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
  20. package/src/react/components/RuntimeInfoFooter.js +164 -104
  21. package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
  22. package/src/react/components/UnifiedPaymentBar.js +7 -4
  23. package/src/react/components/UserAvatar.js +160 -0
  24. package/src/react/components/integrations/IntegrationHero/index.js +48 -0
  25. package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
  26. package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
  27. package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
  28. package/src/react/config/deviceConfigBootstrap.js +5 -3
  29. package/src/react/pages/Connections.js +229 -0
  30. package/src/react/pages/Connections.styles.js +222 -0
  31. package/src/react/pages/DeviceDetailPage.js +2865 -0
  32. package/src/react/pages/DeviceDetailPage.styles.js +308 -0
  33. package/src/react/pages/Devices/currentDevice.js +194 -0
  34. package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
  35. package/src/react/pages/Devices/deviceTypes/all.js +23 -0
  36. package/src/react/pages/Devices/deviceTypes/device.js +25 -0
  37. package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
  38. package/src/react/pages/Devices/deviceTypes/display.js +26 -0
  39. package/src/react/pages/Devices/deviceTypes/index.js +145 -0
  40. package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
  41. package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
  42. package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
  43. package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
  44. package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
  45. package/src/react/pages/Devices.js +183 -0
  46. package/src/react/pages/Devices.styles.js +353 -0
  47. package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
  48. package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
  49. package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
  50. package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
  51. package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
  52. package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
  53. package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
  54. package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
  55. package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
  56. package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
  57. package/src/react/pages/Food99IntegrationPage.js +1 -0
  58. package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
  59. package/src/react/pages/GenericLogPage.js +42 -167
  60. package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
  61. package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
  62. package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
  63. package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
  64. package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
  65. package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
  66. package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
  67. package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
  68. package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
  69. package/src/react/pages/IFoodIntegrationPage.js +1 -0
  70. package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
  71. package/src/react/pages/Imports.js +214 -166
  72. package/src/react/pages/IntegrationConfigPage.js +744 -0
  73. package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
  74. package/src/react/pages/Integrations.js +299 -0
  75. package/src/react/pages/Integrations.styles.js +130 -0
  76. package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
  77. package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
  78. package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
  79. package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
  80. package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
  81. package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
  82. package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
  83. package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
  84. package/src/react/pages/PrinterDeviceFormPage.js +521 -0
  85. package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
  86. package/src/react/pages/WhatsAppConnectionPage.js +459 -0
  87. package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
  88. package/src/react/pages/integrationsCatalog.js +399 -0
  89. package/src/react/services/Cielo/Cielo.js +193 -193
  90. package/src/react/utils/categoryContexts.js +46 -0
  91. package/src/react/utils/entityDisplay.js +222 -217
  92. package/src/react/utils/fileUrl.js +39 -20
  93. package/src/react/utils/integrationPage.js +16 -0
  94. package/src/react/utils/runtimeFooter.js +40 -22
  95. package/src/react/utils/runtimeLanguage.js +50 -9
  96. package/src/react/utils/runtimeMenu.js +191 -131
  97. package/src/react/utils/runtimeZoom.js +43 -0
  98. package/src/react/utils/shopConfig.js +27 -15
  99. package/src/react/utils/shopFranchises.js +126 -1
  100. package/src/react/utils/storeColumns.js +23 -4
  101. package/src/react/utils/userAvatar.js +104 -0
  102. package/src/store/categories/index.js +48 -20
  103. package/src/store/device_config/index.js +62 -21
  104. package/src/store/entity_log/index.js +22 -1
  105. package/src/store/imports/customActions.js +51 -0
  106. package/src/store/imports/index.js +89 -88
  107. package/src/store/status/index.js +11 -11
  108. package/src/store/themes/index.js +22 -0
  109. package/src/store/timezones/index.js +3 -1
  110. package/src/tests/browser/manager/connections.spec.js +143 -0
  111. package/src/tests/browser/manager/devices-current.spec.js +343 -0
  112. package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
  113. package/src/tests/react/api/deviceHeaders.test.js +114 -0
  114. package/src/tests/react/components/AddImportModal.test.js +166 -0
  115. package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
  116. package/src/tests/react/components/UserAvatar.test.js +52 -0
  117. package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
  118. package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
  119. package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
  120. package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
  121. package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
  122. package/src/tests/react/services/Cielo.test.js +190 -190
  123. package/src/tests/react/store/categoriesOrdering.test.js +26 -0
  124. package/src/tests/react/utils/appDomain.test.js +62 -0
  125. package/src/tests/react/utils/entityDisplay.test.js +2 -2
  126. package/src/tests/react/utils/fileUrl.test.js +3 -3
  127. package/src/tests/react/utils/formatter.test.mjs +33 -0
  128. package/src/tests/react/utils/runtimeFooter.test.js +29 -4
  129. package/src/tests/react/utils/runtimeLanguage.test.js +79 -9
  130. package/src/tests/react/utils/runtimeMenu.test.js +198 -134
  131. package/src/tests/react/utils/runtimeZoom.test.js +38 -0
  132. package/src/tests/react/utils/shopConfig.test.js +10 -9
  133. package/src/tests/react/utils/shopFranchises.test.js +43 -0
  134. package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
  135. package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
  136. package/src/tests/react/utils/translate.test.mjs +141 -349
  137. package/src/tests/react/utils/userAvatar.test.js +63 -0
  138. package/src/utils/appDomain.js +15 -4
  139. package/src/utils/formatter.js +104 -16
  140. package/src/utils/integrationConfigs.js +80 -80
  141. package/src/utils/translate.js +222 -438
  142. package/src/utils/translateDiscovery.js +96 -0
  143. package/src/utils/translateNormalize.js +33 -0
  144. package/src/utils/translatePending.js +92 -0
  145. package/src/utils/translateStorage.js +142 -0
  146. package/.github/agents/developer.agent.md +0 -30
  147. package/.github/agents/devops.agent.md +0 -30
  148. package/.github/agents/qa.agent.md +0 -30
  149. package/.github/agents/security.agent.md +0 -30
  150. package/.github/workflows/npmjs.yml +0 -35
@@ -0,0 +1,1916 @@
1
+ import React, {useCallback, useMemo, useState} from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ Alert,
5
+ Platform,
6
+ ScrollView,
7
+ Text,
8
+ TextInput,
9
+ TouchableOpacity,
10
+ View,
11
+ } from 'react-native';
12
+ import {SafeAreaView} from 'react-native-safe-area-context';
13
+ import {useFocusEffect, useNavigation, useRoute} from '@react-navigation/native';
14
+ import {Picker} from '@react-native-picker/picker';
15
+ import Icon from 'react-native-vector-icons/Feather';
16
+ import {useStore} from '@store';
17
+ import {api} from '@controleonline/ui-common/src/api';
18
+ import StateStore from '@controleonline/ui-common/src/react/components/StateStore';
19
+ import {resolveThemePalette, withOpacity} from '@controleonline/../../src/styles/branding';
20
+ import {colors} from '@controleonline/../../src/styles/colors';
21
+ import {resolveFileImageUrl} from '@controleonline/ui-common/src/react/utils/fileUrl';
22
+ import {buildOrderProductCards} from '@controleonline/ui-orders/src/react/components/OrderProducts.utils';
23
+ import {resolveMarketplaceOrderCode} from '@controleonline/ui-orders/src/react/utils/orderIdentity';
24
+ import {
25
+ hasOrderProducts,
26
+ needsDetailedOrderProductsFetch,
27
+ } from '@controleonline/ui-orders/src/react/utils/orderProductsFetchPolicy';
28
+ import {getNetworkDeviceProfile} from '@controleonline/ui-common/src/react/utils/networkDeviceProfiles';
29
+ import {
30
+ buildNetworkCameraConfigs,
31
+ buildNetworkCameraMetadata,
32
+ DEFAULT_NETWORK_CAMERA_PROTOCOL,
33
+ getCameraMetadataField,
34
+ getNetworkCameraConfigValues,
35
+ NETWORK_CAMERA_PORT_CONFIG_KEY,
36
+ NETWORK_CAMERA_PROTOCOL_OPTIONS,
37
+ } from '@controleonline/ui-common/src/react/utils/networkCameraDevices';
38
+ import {
39
+ DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY,
40
+ isTruthyValue,
41
+ parseConfigsObject,
42
+ } from '@controleonline/ui-common/src/react/config/deviceConfigBootstrap';
43
+ import {
44
+ checkNetworkPrinterConnection,
45
+ isNetworkPrinterRuntimeSupported,
46
+ } from '@controleonline/ui-common/src/react/services/NetworkPrinterService';
47
+ import {
48
+ buildNetworkPrinterMetadata,
49
+ DEFAULT_NETWORK_PRINTER_COLUMNS,
50
+ DEFAULT_NETWORK_PRINTER_CODE_PAGE,
51
+ DEFAULT_NETWORK_PRINTER_PORT,
52
+ DEFAULT_NETWORK_PRINTER_TRANSPORT,
53
+ getDeviceTypeLabel,
54
+ getPrinterManagerDeviceOptions,
55
+ getPrinterMetadataField,
56
+ IP_CAMERA_DEVICE_TYPE,
57
+ NETWORK_PRINTER_CODE_PAGE_CONFIG_KEY,
58
+ NETWORK_PRINTER_COLUMNS_CONFIG_KEY,
59
+ NETWORK_PRINTER_MANAGER_DEVICE_CONFIG_KEY,
60
+ NETWORK_PRINTER_PORT_CONFIG_KEY,
61
+ NETWORK_PRINTER_TRANSPORT_CONFIG_KEY,
62
+ normalizePrinterColumns,
63
+ normalizePrinterHost,
64
+ normalizePrinterPort,
65
+ PRINT_DEVICE_TYPE,
66
+ } from '@controleonline/ui-common/src/react/utils/printerDevices';
67
+ import {
68
+ filterDeviceConfigsByCompany,
69
+ normalizeDeviceId,
70
+ } from '@controleonline/ui-common/src/react/utils/paymentDevices';
71
+ import NetworkPrinterPreviewModal from '../components/NetworkPrinterPreviewModal';
72
+ import styles from './PrinterDeviceDetailPage.styles';
73
+
74
+ const resolveErrorMessage = error =>
75
+ error?.response?.data?.['hydra:description'] ||
76
+ error?.response?.data?.message ||
77
+ error?.message ||
78
+ 'Nao foi possivel salvar as alteracoes.';
79
+
80
+ const extractCollectionMembers = response => {
81
+ if (Array.isArray(response)) {
82
+ return response;
83
+ }
84
+
85
+ if (Array.isArray(response?.member)) {
86
+ return response.member;
87
+ }
88
+
89
+ if (Array.isArray(response?.['hydra:member'])) {
90
+ return response['hydra:member'];
91
+ }
92
+
93
+ return [];
94
+ };
95
+
96
+ const normalizeEntityId = value => {
97
+ if (!value) {
98
+ return '';
99
+ }
100
+
101
+ if (typeof value === 'number') {
102
+ return String(value);
103
+ }
104
+
105
+ if (typeof value === 'string') {
106
+ return value.replace(/\D+/g, '');
107
+ }
108
+
109
+ return normalizeEntityId(value?.id || value?.['@id']);
110
+ };
111
+
112
+ const normalizePreviewText = value => String(value || '').trim();
113
+
114
+ const ORDER_PRINT_FOOTER_TEXT_CONFIG_KEY = 'order-print-footer-text';
115
+ const ORDER_PREVIEW_CHANNELS = {
116
+ food99: {
117
+ app: 'Food99',
118
+ label: 'Food99',
119
+ stateEndpoint: orderId => `marketplace/integrations/99food/orders/${orderId}/state`,
120
+ },
121
+ ifood: {
122
+ app: 'iFood',
123
+ label: 'iFood',
124
+ stateEndpoint: orderId => `marketplace/integrations/ifood/orders/${orderId}/state`,
125
+ },
126
+ };
127
+
128
+ const NETWORK_PRINTER_CODE_PAGE_OPTIONS = [
129
+ {value: 'cp850', label: 'CP850 - portugues/Bematech'},
130
+ {value: 'latin1', label: 'Latin-1 / Windows-1252'},
131
+ ];
132
+
133
+ const formatPreviewMoney = value => {
134
+ const numericValue = Number(value || 0);
135
+ if (!Number.isFinite(numericValue)) {
136
+ return '0,00';
137
+ }
138
+
139
+ return numericValue.toLocaleString('pt-BR', {
140
+ minimumFractionDigits: 2,
141
+ maximumFractionDigits: 2,
142
+ });
143
+ };
144
+
145
+ const upperPreviewText = value =>
146
+ normalizePreviewText(value)
147
+ .normalize('NFD')
148
+ .replace(/[\u0300-\u036f]/g, '')
149
+ .toUpperCase();
150
+
151
+ const previewLine = (text, options = {}) => ({
152
+ ...options,
153
+ text: normalizePreviewText(text),
154
+ });
155
+
156
+ const getEmbeddedOrderProducts = order => {
157
+ if (Array.isArray(order?.orderProducts)) {
158
+ return order.orderProducts;
159
+ }
160
+
161
+ if (Array.isArray(order?.orderProducts?.member)) {
162
+ return order.orderProducts.member;
163
+ }
164
+
165
+ if (Array.isArray(order?.orderProducts?.['hydra:member'])) {
166
+ return order.orderProducts['hydra:member'];
167
+ }
168
+
169
+ return [];
170
+ };
171
+
172
+ const normalizeConfigEntries = response => {
173
+ const rawConfigs =
174
+ response?.configs ?? response?.member ?? response?.['hydra:member'] ?? response;
175
+
176
+ if (Array.isArray(rawConfigs)) {
177
+ return rawConfigs.reduce((configs, config) => {
178
+ const key =
179
+ config?.configKey ||
180
+ config?.key ||
181
+ config?.name ||
182
+ config?.config_name;
183
+ const value =
184
+ config?.configValue !== undefined
185
+ ? config.configValue
186
+ : config?.value !== undefined
187
+ ? config.value
188
+ : config?.config_value;
189
+
190
+ if (key) {
191
+ configs[key] = value;
192
+ }
193
+
194
+ return configs;
195
+ }, {});
196
+ }
197
+
198
+ return rawConfigs && typeof rawConfigs === 'object' ? rawConfigs : {};
199
+ };
200
+
201
+ const getOrderExternalCode = (order, marketplaceState = null) => {
202
+ const marketplaceCode = resolveMarketplaceOrderCode(order, marketplaceState);
203
+ if (marketplaceCode) {
204
+ return marketplaceCode;
205
+ }
206
+
207
+ const app = normalizePreviewText(order?.app);
208
+ const matchingExtraData = (Array.isArray(order?.extraData) ? order.extraData : [])
209
+ .filter(item => {
210
+ const source = normalizePreviewText(item?.source).toLowerCase();
211
+ const fieldName = normalizePreviewText(item?.extra_fields?.name).toLowerCase();
212
+ return source === app.toLowerCase() && ['code', 'id'].includes(fieldName);
213
+ })
214
+ .sort((left, right) => {
215
+ const leftName = normalizePreviewText(left?.extra_fields?.name).toLowerCase();
216
+ const rightName = normalizePreviewText(right?.extra_fields?.name).toLowerCase();
217
+ return (leftName === 'code' ? 0 : 1) - (rightName === 'code' ? 0 : 1);
218
+ })[0];
219
+
220
+ return (
221
+ normalizePreviewText(order?.externalCode) ||
222
+ normalizePreviewText(matchingExtraData?.value) ||
223
+ normalizePreviewText(order?.id)
224
+ );
225
+ };
226
+
227
+ const getOrderCustomerName = order =>
228
+ normalizePreviewText(order?.client?.name) ||
229
+ normalizePreviewText(order?.payer?.name) ||
230
+ 'Cliente';
231
+
232
+ const getOrderChannelName = order => {
233
+ const app = normalizePreviewText(order?.app);
234
+ if (app.toLowerCase() === 'food99') {
235
+ return 'Food99';
236
+ }
237
+
238
+ if (app.toLowerCase() === 'ifood') {
239
+ return 'iFood';
240
+ }
241
+
242
+ return app || 'PDV';
243
+ };
244
+
245
+ const getCompanyBrandTitle = ({company, order, marketplaceState = {}}) =>
246
+ normalizePreviewText(marketplaceState?.merchant?.name) ||
247
+ normalizePreviewText(marketplaceState?.merchant?.merchant_name) ||
248
+ normalizePreviewText(marketplaceState?.shop?.shop_name) ||
249
+ normalizePreviewText(marketplaceState?.shop?.merchant_name) ||
250
+ normalizePreviewText(marketplaceState?.store?.name) ||
251
+ normalizePreviewText(marketplaceState?.integration?.merchant_name) ||
252
+ normalizePreviewText(marketplaceState?.integration?.store_name) ||
253
+ normalizePreviewText(marketplaceState?.order?.merchant?.name) ||
254
+ normalizePreviewText(company?.alias) ||
255
+ normalizePreviewText(order?.provider?.alias) ||
256
+ normalizePreviewText(company?.name) ||
257
+ normalizePreviewText(order?.provider?.name);
258
+
259
+ const getNestedAddressValue = (address, paths) => {
260
+ for (const path of paths) {
261
+ const value = path
262
+ .split('.')
263
+ .reduce((currentValue, key) => currentValue?.[key], address);
264
+ const normalizedValue = normalizePreviewText(value);
265
+
266
+ if (normalizedValue) {
267
+ return normalizedValue;
268
+ }
269
+ }
270
+
271
+ return '';
272
+ };
273
+
274
+ const buildAddressLinesFromAddress = address => {
275
+ if (!address) {
276
+ return [];
277
+ }
278
+
279
+ const streetLine = [
280
+ getNestedAddressValue(address, [
281
+ 'street.street',
282
+ 'streetName',
283
+ 'street_name',
284
+ 'street',
285
+ 'address',
286
+ ]),
287
+ getNestedAddressValue(address, ['number', 'streetNumber', 'street_number']),
288
+ ]
289
+ .map(normalizePreviewText)
290
+ .filter(Boolean)
291
+ .join(', ');
292
+ const districtLine = [
293
+ getNestedAddressValue(address, [
294
+ 'street.district.district',
295
+ 'district.district',
296
+ 'neighborhood',
297
+ 'district',
298
+ ]),
299
+ getNestedAddressValue(address, [
300
+ 'street.cep.cep',
301
+ 'cep.cep',
302
+ 'zipcode',
303
+ 'zipCode',
304
+ 'postalCode',
305
+ 'postal_code',
306
+ ]),
307
+ ]
308
+ .map(normalizePreviewText)
309
+ .filter(Boolean)
310
+ .join(', ');
311
+ const complementLine = getNestedAddressValue(address, [
312
+ 'complement',
313
+ 'reference',
314
+ 'details',
315
+ ]);
316
+
317
+ return [streetLine, districtLine, complementLine].filter(Boolean);
318
+ };
319
+
320
+ const getOrderAddressLines = (order, marketplaceState = {}) => {
321
+ const addressCandidates = [
322
+ marketplaceState?.address,
323
+ marketplaceState?.delivery?.address,
324
+ order?.addressDestination,
325
+ order?.delivery?.address,
326
+ order?.client?.address,
327
+ ];
328
+
329
+ for (const address of addressCandidates) {
330
+ const lines = buildAddressLinesFromAddress(address);
331
+ if (lines.length > 0) {
332
+ return lines;
333
+ }
334
+ }
335
+
336
+ return [];
337
+ };
338
+
339
+ const getCardQueueName = card =>
340
+ normalizePreviewText(card?.queuePresentation?.queueLabel) ||
341
+ normalizePreviewText(card?.queuePresentation?.queue?.queue) ||
342
+ normalizePreviewText(card?.queuePresentation?.queue?.name) ||
343
+ 'Fila sem nome';
344
+
345
+ const getMarketplaceDelivery = marketplaceState => marketplaceState?.delivery || {};
346
+
347
+ const getMarketplacePayment = marketplaceState => marketplaceState?.payment || {};
348
+
349
+ const getOrderOperationLabel = (order, marketplaceState = {}) => {
350
+ const channelName = getOrderChannelName(order);
351
+ const delivery = getMarketplaceDelivery(marketplaceState);
352
+ const deliveryLabel =
353
+ normalizePreviewText(delivery?.delivery_label) ||
354
+ normalizePreviewText(delivery?.mode_label) ||
355
+ normalizePreviewText(delivery?.type_label) ||
356
+ normalizePreviewText(delivery?.type) ||
357
+ 'Entrega';
358
+
359
+ return `${channelName} - ${deliveryLabel}`;
360
+ };
361
+
362
+ const getDeliveryPeopleName = marketplaceState => {
363
+ const delivery = getMarketplaceDelivery(marketplaceState);
364
+
365
+ return (
366
+ normalizePreviewText(delivery?.rider_name) ||
367
+ normalizePreviewText(delivery?.courier_name) ||
368
+ normalizePreviewText(delivery?.delivery_people?.name) ||
369
+ normalizePreviewText(delivery?.provider?.name)
370
+ );
371
+ };
372
+
373
+ const getDeliveryCode = marketplaceState => {
374
+ const delivery = getMarketplaceDelivery(marketplaceState);
375
+ const identifiers = marketplaceState?.identifiers || {};
376
+
377
+ return (
378
+ normalizePreviewText(delivery?.handover_code) ||
379
+ normalizePreviewText(delivery?.pickup_code) ||
380
+ normalizePreviewText(delivery?.locator) ||
381
+ normalizePreviewText(identifiers?.handover_code) ||
382
+ normalizePreviewText(identifiers?.pickup_code) ||
383
+ normalizePreviewText(identifiers?.locator)
384
+ );
385
+ };
386
+
387
+ const getPaymentLine = marketplaceState => {
388
+ const payment = getMarketplacePayment(marketplaceState);
389
+ const hasPaymentContext = Object.keys(payment).length > 0;
390
+ const collectAmount = Number(
391
+ payment?.collect_on_delivery_amount || payment?.amount_pending || 0,
392
+ );
393
+ const isPaidOnline =
394
+ payment?.is_paid_online === true ||
395
+ payment?.is_fully_paid === true ||
396
+ normalizePreviewText(payment?.pay_type).toLowerCase() === 'online';
397
+
398
+ if (!hasPaymentContext) {
399
+ return 'PAGAMENTO NAO INFORMADO';
400
+ }
401
+
402
+ if (isPaidOnline || collectAmount <= 0) {
403
+ return 'PAGO ONLINE - NAO COBRAR NA ENTREGA';
404
+ }
405
+
406
+ if (collectAmount > 0) {
407
+ return `COBRAR NA ENTREGA R$ ${formatPreviewMoney(collectAmount)}`;
408
+ }
409
+
410
+ return (
411
+ normalizePreviewText(payment?.selected_payment_label) ||
412
+ normalizePreviewText(payment?.pay_method) ||
413
+ 'PAGAMENTO NAO INFORMADO'
414
+ );
415
+ };
416
+
417
+ const getCardQuantity = card => {
418
+ const quantity = Number(card?.quantity || 1);
419
+ return Number.isFinite(quantity) && quantity > 0 ? quantity : 1;
420
+ };
421
+
422
+ const getOrderItemsCount = cards =>
423
+ (Array.isArray(cards) ? cards : []).reduce(
424
+ (total, card) => total + getCardQuantity(card),
425
+ 0,
426
+ );
427
+
428
+ const appendPreviewGroupLines = (group, lines, level = 0) => {
429
+ const groupLabel = upperPreviewText(group?.label);
430
+ const indent = ' '.repeat(Math.min(level * 2, 6));
431
+
432
+ if (groupLabel) {
433
+ lines.push(previewLine(`${indent}${groupLabel}:`, {bold: true}));
434
+ }
435
+
436
+ (Array.isArray(group?.items) ? group.items : []).forEach(item => {
437
+ const quantity = Number(item?.quantity || 0);
438
+ const quantityText = quantity > 1 ? `${quantity}x ` : '';
439
+ lines.push(`${indent}* ${quantityText}${item?.name || 'Item'}`);
440
+
441
+ if (item?.observation) {
442
+ lines.push(previewLine(`${indent}OBS: ${item.observation}`, {reverse: true}));
443
+ }
444
+
445
+ (Array.isArray(item?.groups) ? item.groups : []).forEach(childGroup => {
446
+ appendPreviewGroupLines(childGroup, lines, level + 1);
447
+ });
448
+ });
449
+ };
450
+
451
+ const appendPreviewCardLines = ({card, lines}) => {
452
+ const quantity = getCardQuantity(card);
453
+ const quantityText = `${quantity}x `;
454
+
455
+ lines.push(previewLine(`${quantityText}${card?.name || 'Produto'}`, {bold: true}));
456
+
457
+ if (card?.observation) {
458
+ lines.push(previewLine(`OBS: ${card.observation}`, {reverse: true}));
459
+ }
460
+
461
+ (Array.isArray(card?.groups) ? card.groups : []).forEach(group => {
462
+ appendPreviewGroupLines(group, lines);
463
+ });
464
+ };
465
+
466
+ const buildQueueGroupsFromCards = cards => {
467
+ const groupedCards = new Map();
468
+
469
+ (Array.isArray(cards) ? cards : []).forEach(card => {
470
+ const queueName = getCardQueueName(card);
471
+ groupedCards.set(queueName, [...(groupedCards.get(queueName) || []), card]);
472
+ });
473
+
474
+ return Array.from(groupedCards.entries()).map(([queueName, items]) => ({
475
+ queueName,
476
+ items,
477
+ }));
478
+ };
479
+
480
+ const buildOrderDocuments = ({
481
+ order,
482
+ orderProducts,
483
+ columns,
484
+ company,
485
+ marketplaceState = {},
486
+ footerText = '',
487
+ }) => {
488
+ const divider = '-'.repeat(Number(columns || 48));
489
+ const externalCode = getOrderExternalCode(order, marketplaceState);
490
+ const channelName = getOrderChannelName(order);
491
+ const orderProductCards = buildOrderProductCards(orderProducts);
492
+ const groupedQueueItems = buildQueueGroupsFromCards(orderProductCards);
493
+ const companyName =
494
+ normalizePreviewText(company?.name) ||
495
+ normalizePreviewText(order?.provider?.name) ||
496
+ normalizePreviewText(company?.alias);
497
+ const brandTitle = getCompanyBrandTitle({company, order, marketplaceState});
498
+ const shouldShowCompanyName =
499
+ companyName &&
500
+ brandTitle.toLocaleLowerCase() !== companyName.toLocaleLowerCase();
501
+ const customerName = upperPreviewText(getOrderCustomerName(order));
502
+ const footerLine = normalizePreviewText(footerText);
503
+ const addressLines = getOrderAddressLines(order, marketplaceState);
504
+ const deliveryPeopleName = getDeliveryPeopleName(marketplaceState);
505
+ const deliveryCode = getDeliveryCode(marketplaceState);
506
+ const itemsCount = getOrderItemsCount(orderProductCards);
507
+
508
+ const orderHeaderLines = [
509
+ ...(brandTitle ? [previewLine(brandTitle, {center: true, bold: true})] : []),
510
+ ...(shouldShowCompanyName ? [previewLine(companyName, {center: true})] : []),
511
+ divider,
512
+ previewLine(`Pedido #${order?.id || ''} - ${channelName}`, {center: true}),
513
+ previewLine(upperPreviewText(getOrderOperationLabel(order, marketplaceState)), {
514
+ center: true,
515
+ }),
516
+ previewLine(`PEDIDO #${externalCode}`, {center: true, bold: true, reverse: true, large: true}),
517
+ previewLine(customerName, {center: true, bold: true}),
518
+ divider,
519
+ ...addressLines,
520
+ ...(addressLines.length > 0 ? [divider] : []),
521
+ ...(deliveryPeopleName ? [`Entregador: ${deliveryPeopleName}`] : []),
522
+ ...(deliveryCode
523
+ ? [
524
+ 'CODIGO DE ENTREGA/COLETA:',
525
+ previewLine(deliveryCode, {reverse: true}),
526
+ ]
527
+ : []),
528
+ ...(deliveryPeopleName || deliveryCode ? [getPaymentLine(marketplaceState), divider] : []),
529
+ previewLine(`ITENS DO PEDIDO (${itemsCount})`, {bold: true}),
530
+ ...orderProductCards.flatMap(card => {
531
+ const lines = [];
532
+ appendPreviewCardLines({card, lines});
533
+ lines.push(divider);
534
+ return lines;
535
+ }),
536
+ ...(deliveryPeopleName || deliveryCode ? [] : [getPaymentLine(marketplaceState)]),
537
+ ...(footerLine ? [divider, footerLine] : []),
538
+ ];
539
+
540
+ return [
541
+ {
542
+ title: 'Pedido completo',
543
+ subtitle: `${channelName} #${externalCode}`,
544
+ hideDefaultHeader: true,
545
+ lines: orderHeaderLines,
546
+ },
547
+ ...groupedQueueItems.map(group => ({
548
+ title: group.queueName,
549
+ subtitle: `Pedido #${order?.id || ''} - ${channelName}`,
550
+ hideDefaultHeader: true,
551
+ hideLogo: true,
552
+ lines: [
553
+ previewLine(group.queueName, {center: true, bold: true}),
554
+ previewLine(upperPreviewText(channelName), {center: true}),
555
+ previewLine(`PEDIDO #${externalCode}`, {
556
+ center: true,
557
+ bold: true,
558
+ reverse: true,
559
+ }),
560
+ previewLine(customerName, {center: true, bold: true}),
561
+ divider,
562
+ ...group.items.flatMap(card => {
563
+ const lines = [];
564
+ appendPreviewCardLines({card, lines});
565
+ lines.push(divider);
566
+ return lines;
567
+ }),
568
+ ],
569
+ })),
570
+ ];
571
+ };
572
+
573
+ const getConnectionStatusMeta = status => {
574
+ if (status === 'online') {
575
+ return {
576
+ label: 'Online',
577
+ color: '#10b981',
578
+ icon: 'wifi',
579
+ };
580
+ }
581
+
582
+ if (status === 'offline') {
583
+ return {
584
+ label: 'Offline',
585
+ color: '#c10015',
586
+ icon: 'wifi-off',
587
+ };
588
+ }
589
+
590
+ if (status === 'checking') {
591
+ return {
592
+ label: 'Testando',
593
+ color: '#0EA5E9',
594
+ icon: 'loader',
595
+ };
596
+ }
597
+
598
+ if (status === 'unsupported') {
599
+ return {
600
+ label: 'Sem teste',
601
+ color: '#e67e22',
602
+ icon: 'slash',
603
+ };
604
+ }
605
+
606
+ return {
607
+ label: 'Pendente',
608
+ color: '#64748B',
609
+ icon: 'clock',
610
+ };
611
+ };
612
+
613
+ const PrinterDeviceDetailPage = () => {
614
+ const navigation = useNavigation();
615
+ const route = useRoute();
616
+ const {
617
+ dcId: initialDeviceConfigId,
618
+ deviceId,
619
+ deviceString,
620
+ deviceType: initialDeviceType,
621
+ alias: initialAlias,
622
+ configs: initialConfigs,
623
+ metadata: initialMetadata,
624
+ } = route.params || {};
625
+ const normalizedDeviceType =
626
+ String(initialDeviceType || '').trim().toUpperCase() || PRINT_DEVICE_TYPE;
627
+ const isIpCamera = normalizedDeviceType === IP_CAMERA_DEVICE_TYPE;
628
+ const deviceProfile = useMemo(
629
+ () => getNetworkDeviceProfile(normalizedDeviceType),
630
+ [normalizedDeviceType],
631
+ );
632
+
633
+ const peopleStore = useStore('people');
634
+ const deviceStore = useStore('device');
635
+ const deviceConfigStore = useStore('device_config');
636
+ const themeStore = useStore('theme');
637
+
638
+ const {currentCompany} = peopleStore.getters;
639
+ const {colors: themeColors} = themeStore.getters;
640
+
641
+ const brandColors = useMemo(
642
+ () =>
643
+ resolveThemePalette(
644
+ {...themeColors, ...(currentCompany?.theme?.colors || {})},
645
+ colors,
646
+ ),
647
+ [themeColors, currentCompany?.id],
648
+ );
649
+
650
+ const initialParsedConfigs = useMemo(
651
+ () => parseConfigsObject(initialConfigs),
652
+ [initialConfigs],
653
+ );
654
+ const initialCameraConfigValues = useMemo(
655
+ () => getNetworkCameraConfigValues(initialParsedConfigs),
656
+ [initialParsedConfigs],
657
+ );
658
+ const persistedDeviceHost = normalizePrinterHost(deviceString);
659
+
660
+ const [loading, setLoading] = useState(false);
661
+ const [savingDevice, setSavingDevice] = useState(false);
662
+ const [savingConfig, setSavingConfig] = useState(false);
663
+ const [savingRuntimeDebugInfo, setSavingRuntimeDebugInfo] = useState(false);
664
+ const [removingConfig, setRemovingConfig] = useState(false);
665
+ const [companyDeviceConfigs, setCompanyDeviceConfigs] = useState([]);
666
+ const [deviceConfigId, setDeviceConfigId] = useState(
667
+ String(initialDeviceConfigId || '').trim(),
668
+ );
669
+ const [deviceMetadata, setDeviceMetadata] = useState(initialMetadata || {});
670
+ const [alias, setAlias] = useState(initialAlias || '');
671
+ const [deviceHost, setDeviceHost] = useState(persistedDeviceHost);
672
+ const [connectionStatus, setConnectionStatus] = useState(
673
+ isNetworkPrinterRuntimeSupported ? 'idle' : 'unsupported',
674
+ );
675
+ const [connectionMessage, setConnectionMessage] = useState(
676
+ isNetworkPrinterRuntimeSupported
677
+ ? `Aguardando teste do socket da ${deviceProfile.itemLabel.toLowerCase()}.`
678
+ : 'Teste de socket disponivel apenas no app nativo.',
679
+ );
680
+ const [checkingConnection, setCheckingConnection] = useState(false);
681
+ const [printPreviewVisible, setPrintPreviewVisible] = useState(false);
682
+ const [printPreviewDocuments, setPrintPreviewDocuments] = useState([]);
683
+ const [printPreviewError, setPrintPreviewError] = useState('');
684
+ const [printPreviewLoading, setPrintPreviewLoading] = useState(false);
685
+ const [printPreviewLogoUrl, setPrintPreviewLogoUrl] = useState('');
686
+ const [printPreviewSubtitle, setPrintPreviewSubtitle] = useState('');
687
+ const [printPreviewTitle, setPrintPreviewTitle] = useState(
688
+ 'Visualizacao da impressao',
689
+ );
690
+ const [manufacturer, setManufacturer] = useState(
691
+ isIpCamera
692
+ ? getCameraMetadataField(initialMetadata, 'manufacturer')
693
+ : getPrinterMetadataField(initialMetadata, 'manufacturer'),
694
+ );
695
+ const [model, setModel] = useState(
696
+ isIpCamera
697
+ ? getCameraMetadataField(initialMetadata, 'model')
698
+ : getPrinterMetadataField(initialMetadata, 'model'),
699
+ );
700
+ const [version, setVersion] = useState(
701
+ isIpCamera
702
+ ? getCameraMetadataField(initialMetadata, 'version')
703
+ : getPrinterMetadataField(initialMetadata, 'version'),
704
+ );
705
+ const [port, setPort] = useState(
706
+ isIpCamera
707
+ ? initialCameraConfigValues.port
708
+ : normalizePrinterPort(
709
+ initialParsedConfigs[NETWORK_PRINTER_PORT_CONFIG_KEY] ||
710
+ DEFAULT_NETWORK_PRINTER_PORT,
711
+ ),
712
+ );
713
+ const [columns, setColumns] = useState(
714
+ normalizePrinterColumns(
715
+ initialParsedConfigs[NETWORK_PRINTER_COLUMNS_CONFIG_KEY] ||
716
+ DEFAULT_NETWORK_PRINTER_COLUMNS,
717
+ ),
718
+ );
719
+ const [managerDeviceId, setManagerDeviceId] = useState(
720
+ isIpCamera
721
+ ? initialCameraConfigValues.managerDeviceId
722
+ : normalizeDeviceId(
723
+ initialParsedConfigs[NETWORK_PRINTER_MANAGER_DEVICE_CONFIG_KEY],
724
+ ),
725
+ );
726
+ const [transport, setTransport] = useState(
727
+ String(
728
+ initialParsedConfigs[NETWORK_PRINTER_TRANSPORT_CONFIG_KEY] ||
729
+ DEFAULT_NETWORK_PRINTER_TRANSPORT,
730
+ ),
731
+ );
732
+ const [codePage, setCodePage] = useState(
733
+ String(
734
+ initialParsedConfigs[NETWORK_PRINTER_CODE_PAGE_CONFIG_KEY] ||
735
+ DEFAULT_NETWORK_PRINTER_CODE_PAGE,
736
+ ),
737
+ );
738
+ const [protocol, setProtocol] = useState(initialCameraConfigValues.protocol);
739
+ const [streamPath, setStreamPath] = useState(
740
+ initialCameraConfigValues.streamPath,
741
+ );
742
+ const [username, setUsername] = useState(initialCameraConfigValues.username);
743
+ const [password, setPassword] = useState(initialCameraConfigValues.password);
744
+ const [deviceRuntimeDebugInfoEnabled, setDeviceRuntimeDebugInfoEnabled] =
745
+ useState(
746
+ isTruthyValue(
747
+ initialParsedConfigs[DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY],
748
+ ),
749
+ );
750
+
751
+ const pickerMode = Platform.OS === 'android' ? 'dropdown' : undefined;
752
+ const scopedDeviceConfigs = useMemo(
753
+ () => filterDeviceConfigsByCompany(companyDeviceConfigs, currentCompany?.id),
754
+ [companyDeviceConfigs, currentCompany?.id],
755
+ );
756
+ const managerDeviceOptions = useMemo(
757
+ () =>
758
+ getPrinterManagerDeviceOptions({
759
+ deviceConfigs: scopedDeviceConfigs,
760
+ companyId: currentCompany?.id,
761
+ excludeDeviceId: deviceHost || persistedDeviceHost,
762
+ }),
763
+ [currentCompany?.id, deviceHost, persistedDeviceHost, scopedDeviceConfigs],
764
+ );
765
+ const connectionStatusMeta = useMemo(
766
+ () => getConnectionStatusMeta(connectionStatus),
767
+ [connectionStatus],
768
+ );
769
+
770
+ const runConnectionCheck = useCallback(
771
+ async ({hostOverride = null, portOverride = null} = {}) => {
772
+ const normalizedHost = normalizePrinterHost(
773
+ hostOverride ?? deviceHost ?? persistedDeviceHost,
774
+ );
775
+ const normalizedPort = normalizePrinterPort(
776
+ portOverride ?? port ?? DEFAULT_NETWORK_PRINTER_PORT,
777
+ );
778
+
779
+ if (!isNetworkPrinterRuntimeSupported) {
780
+ setConnectionStatus('unsupported');
781
+ setConnectionMessage(
782
+ 'Teste de socket disponivel apenas no app nativo.',
783
+ );
784
+ return false;
785
+ }
786
+
787
+ if (!normalizedHost) {
788
+ setConnectionStatus('offline');
789
+ setConnectionMessage('IP ou hostname nao configurado.');
790
+ return false;
791
+ }
792
+
793
+ setCheckingConnection(true);
794
+ setConnectionStatus('checking');
795
+ setConnectionMessage(
796
+ `Testando socket ${normalizedHost}:${normalizedPort}...`,
797
+ );
798
+
799
+ try {
800
+ await checkNetworkPrinterConnection({
801
+ host: normalizedHost,
802
+ port: normalizedPort,
803
+ });
804
+
805
+ setConnectionStatus('online');
806
+ setConnectionMessage(
807
+ `Socket conectado em ${normalizedHost}:${normalizedPort}.`,
808
+ );
809
+ return true;
810
+ } catch (error) {
811
+ setConnectionStatus('offline');
812
+ setConnectionMessage(
813
+ error?.message ||
814
+ `Falha ao conectar em ${normalizedHost}:${normalizedPort}.`,
815
+ );
816
+ return false;
817
+ } finally {
818
+ setCheckingConnection(false);
819
+ }
820
+ },
821
+ [deviceHost, persistedDeviceHost, port],
822
+ );
823
+
824
+ useFocusEffect(
825
+ useCallback(() => {
826
+ if (!currentCompany?.id || !deviceId) {
827
+ return;
828
+ }
829
+
830
+ let cancelled = false;
831
+ setLoading(true);
832
+
833
+ Promise.all([
834
+ deviceStore.actions.get(deviceId).catch(() => null),
835
+ deviceConfigStore.actions
836
+ .getItems({people: `/people/${currentCompany.id}`})
837
+ .catch(() => []),
838
+ ])
839
+ .then(([deviceData, configs]) => {
840
+ if (cancelled) {
841
+ return;
842
+ }
843
+
844
+ const scopedConfigs = filterDeviceConfigsByCompany(
845
+ configs,
846
+ currentCompany?.id,
847
+ );
848
+ setCompanyDeviceConfigs(scopedConfigs);
849
+
850
+ const normalizedDeviceKey = String(deviceId || '').trim();
851
+ const currentDeviceConfig = scopedConfigs.find(deviceConfig => {
852
+ const currentDeviceId = String(deviceConfig?.device?.id || '').trim();
853
+ const currentDeviceString = normalizeDeviceId(
854
+ deviceConfig?.device?.device,
855
+ );
856
+ const currentDeviceType = String(
857
+ deviceConfig?.type || deviceConfig?.device?.type || '',
858
+ )
859
+ .trim()
860
+ .toUpperCase();
861
+
862
+ return (
863
+ currentDeviceType === normalizedDeviceType &&
864
+ (
865
+ (normalizedDeviceKey !== '' &&
866
+ currentDeviceId === normalizedDeviceKey) ||
867
+ currentDeviceString === normalizeDeviceId(persistedDeviceHost)
868
+ )
869
+ );
870
+ });
871
+ const nextConfigs = parseConfigsObject(currentDeviceConfig?.configs);
872
+ const nextCameraConfigValues =
873
+ getNetworkCameraConfigValues(nextConfigs);
874
+ const nextMetadata =
875
+ deviceData?.metadata ||
876
+ currentDeviceConfig?.device?.metadata ||
877
+ initialMetadata;
878
+ const nextDeviceHost = normalizePrinterHost(
879
+ deviceData?.device || currentDeviceConfig?.device?.device || deviceString,
880
+ );
881
+
882
+ setDeviceConfigId(
883
+ String(currentDeviceConfig?.id || initialDeviceConfigId || '').trim(),
884
+ );
885
+ setDeviceMetadata(nextMetadata || {});
886
+ setAlias(
887
+ deviceData?.alias ||
888
+ currentDeviceConfig?.device?.alias ||
889
+ initialAlias ||
890
+ '',
891
+ );
892
+ setDeviceHost(nextDeviceHost);
893
+ setManufacturer(
894
+ isIpCamera
895
+ ? getCameraMetadataField(nextMetadata, 'manufacturer')
896
+ : getPrinterMetadataField(nextMetadata, 'manufacturer'),
897
+ );
898
+ setModel(
899
+ isIpCamera
900
+ ? getCameraMetadataField(nextMetadata, 'model')
901
+ : getPrinterMetadataField(nextMetadata, 'model'),
902
+ );
903
+ setVersion(
904
+ isIpCamera
905
+ ? getCameraMetadataField(nextMetadata, 'version')
906
+ : getPrinterMetadataField(nextMetadata, 'version'),
907
+ );
908
+ setPort(
909
+ isIpCamera
910
+ ? nextCameraConfigValues.port
911
+ : normalizePrinterPort(
912
+ nextConfigs[NETWORK_PRINTER_PORT_CONFIG_KEY] ||
913
+ DEFAULT_NETWORK_PRINTER_PORT,
914
+ ),
915
+ );
916
+ setColumns(
917
+ normalizePrinterColumns(
918
+ nextConfigs[NETWORK_PRINTER_COLUMNS_CONFIG_KEY] ||
919
+ DEFAULT_NETWORK_PRINTER_COLUMNS,
920
+ ),
921
+ );
922
+ setManagerDeviceId(
923
+ isIpCamera
924
+ ? nextCameraConfigValues.managerDeviceId
925
+ : normalizeDeviceId(
926
+ nextConfigs[NETWORK_PRINTER_MANAGER_DEVICE_CONFIG_KEY],
927
+ ),
928
+ );
929
+ setTransport(
930
+ String(
931
+ nextConfigs[NETWORK_PRINTER_TRANSPORT_CONFIG_KEY] ||
932
+ DEFAULT_NETWORK_PRINTER_TRANSPORT,
933
+ ),
934
+ );
935
+ setCodePage(
936
+ String(
937
+ nextConfigs[NETWORK_PRINTER_CODE_PAGE_CONFIG_KEY] ||
938
+ DEFAULT_NETWORK_PRINTER_CODE_PAGE,
939
+ ),
940
+ );
941
+ setProtocol(nextCameraConfigValues.protocol);
942
+ setStreamPath(nextCameraConfigValues.streamPath);
943
+ setUsername(nextCameraConfigValues.username);
944
+ setPassword(nextCameraConfigValues.password);
945
+ setDeviceRuntimeDebugInfoEnabled(
946
+ isTruthyValue(
947
+ nextConfigs[DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY],
948
+ ),
949
+ );
950
+ })
951
+ .finally(() => {
952
+ if (!cancelled) {
953
+ setLoading(false);
954
+ }
955
+ });
956
+
957
+ return () => {
958
+ cancelled = true;
959
+ };
960
+ }, [
961
+ currentCompany?.id,
962
+ deviceConfigStore.actions,
963
+ deviceId,
964
+ deviceStore.actions,
965
+ deviceString,
966
+ initialAlias,
967
+ initialDeviceConfigId,
968
+ initialMetadata,
969
+ isIpCamera,
970
+ persistedDeviceHost,
971
+ ]),
972
+ );
973
+
974
+ useFocusEffect(
975
+ useCallback(() => {
976
+ runConnectionCheck();
977
+ }, [runConnectionCheck]),
978
+ );
979
+
980
+ const saveDeviceRegistration = useCallback(async () => {
981
+ const normalizedHost = normalizePrinterHost(deviceHost);
982
+ if (!normalizedHost) {
983
+ Alert.alert(
984
+ deviceProfile.registrationAlertTitle,
985
+ deviceProfile.hostMissingMessage,
986
+ );
987
+ return;
988
+ }
989
+
990
+ const normalizedAlias = String(alias || '').trim() || normalizedHost;
991
+ const metadata = isIpCamera
992
+ ? buildNetworkCameraMetadata({
993
+ existingMetadata: deviceMetadata,
994
+ host: normalizedHost,
995
+ manufacturer,
996
+ model,
997
+ version,
998
+ })
999
+ : buildNetworkPrinterMetadata({
1000
+ existingMetadata: deviceMetadata,
1001
+ host: normalizedHost,
1002
+ manufacturer,
1003
+ model,
1004
+ version,
1005
+ transport,
1006
+ });
1007
+
1008
+ setSavingDevice(true);
1009
+
1010
+ try {
1011
+ const savedDevice = await deviceStore.actions.save({
1012
+ id: deviceId,
1013
+ alias: normalizedAlias,
1014
+ device: normalizedHost,
1015
+ metadata,
1016
+ });
1017
+ const nextDeviceHost = normalizePrinterHost(
1018
+ savedDevice?.device || normalizedHost,
1019
+ );
1020
+
1021
+ setAlias(savedDevice?.alias || normalizedAlias);
1022
+ setDeviceHost(nextDeviceHost);
1023
+ setDeviceMetadata(savedDevice?.metadata || metadata);
1024
+ navigation.setParams({
1025
+ deviceString: nextDeviceHost,
1026
+ alias: savedDevice?.alias || normalizedAlias,
1027
+ metadata: savedDevice?.metadata || metadata,
1028
+ });
1029
+ runConnectionCheck({
1030
+ hostOverride: nextDeviceHost,
1031
+ portOverride: port,
1032
+ });
1033
+ } catch (error) {
1034
+ Alert.alert(
1035
+ deviceProfile.registrationAlertTitle,
1036
+ resolveErrorMessage(error),
1037
+ );
1038
+ } finally {
1039
+ setSavingDevice(false);
1040
+ }
1041
+ }, [
1042
+ alias,
1043
+ deviceHost,
1044
+ deviceMetadata,
1045
+ deviceId,
1046
+ deviceStore.actions,
1047
+ manufacturer,
1048
+ model,
1049
+ navigation,
1050
+ isIpCamera,
1051
+ transport,
1052
+ version,
1053
+ port,
1054
+ runConnectionCheck,
1055
+ deviceProfile,
1056
+ ]);
1057
+
1058
+ const saveRuntimeDebugInfoConfig = useCallback(async () => {
1059
+ if (!currentCompany?.id) {
1060
+ Alert.alert(
1061
+ 'Empresa nao selecionada',
1062
+ 'Selecione uma empresa antes de salvar as configuracoes.',
1063
+ );
1064
+ return;
1065
+ }
1066
+
1067
+ const normalizedHost = normalizePrinterHost(deviceHost);
1068
+ if (!normalizedHost) {
1069
+ Alert.alert(
1070
+ 'Rodapé do sistema',
1071
+ deviceProfile.hostMissingBeforeSaveMessage,
1072
+ );
1073
+ return;
1074
+ }
1075
+
1076
+ setSavingRuntimeDebugInfo(true);
1077
+
1078
+ try {
1079
+ const savedDeviceConfig = await deviceConfigStore.actions.addDeviceConfigs({
1080
+ device: normalizedHost,
1081
+ people: `/people/${currentCompany.id}`,
1082
+ type: normalizedDeviceType,
1083
+ configs: JSON.stringify({
1084
+ [DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY]:
1085
+ deviceRuntimeDebugInfoEnabled ? '1' : '0',
1086
+ }),
1087
+ });
1088
+
1089
+ setDeviceConfigId(String(savedDeviceConfig?.id || deviceConfigId || '').trim());
1090
+ navigation.setParams({
1091
+ dcId: savedDeviceConfig?.id || deviceConfigId,
1092
+ deviceString: normalizedHost,
1093
+ });
1094
+ } catch (error) {
1095
+ Alert.alert('Rodapé do sistema', resolveErrorMessage(error));
1096
+ } finally {
1097
+ setSavingRuntimeDebugInfo(false);
1098
+ }
1099
+ }, [
1100
+ currentCompany?.id,
1101
+ deviceConfigStore.actions,
1102
+ deviceConfigId,
1103
+ deviceHost,
1104
+ deviceRuntimeDebugInfoEnabled,
1105
+ navigation,
1106
+ deviceProfile,
1107
+ ]);
1108
+
1109
+ const savePrinterConfig = useCallback(async () => {
1110
+ if (!currentCompany?.id) {
1111
+ Alert.alert(
1112
+ 'Empresa nao selecionada',
1113
+ 'Selecione uma empresa antes de salvar as configuracoes.',
1114
+ );
1115
+ return;
1116
+ }
1117
+
1118
+ if (!managerDeviceId) {
1119
+ Alert.alert(
1120
+ 'Device responsavel',
1121
+ deviceProfile.managerRoutingMessage,
1122
+ );
1123
+ return;
1124
+ }
1125
+
1126
+ const normalizedHost = normalizePrinterHost(deviceHost);
1127
+ if (!normalizedHost) {
1128
+ Alert.alert(
1129
+ deviceProfile.routingAlertTitle,
1130
+ deviceProfile.hostMissingMessage,
1131
+ );
1132
+ return;
1133
+ }
1134
+
1135
+ if (normalizedHost !== persistedDeviceHost) {
1136
+ Alert.alert(
1137
+ deviceProfile.routingAlertTitle,
1138
+ deviceProfile.saveBeforeRoutingMessage,
1139
+ );
1140
+ return;
1141
+ }
1142
+
1143
+ const nextConfigs = isIpCamera
1144
+ ? buildNetworkCameraConfigs({
1145
+ managerDeviceId,
1146
+ port,
1147
+ protocol,
1148
+ streamPath,
1149
+ username,
1150
+ password,
1151
+ })
1152
+ : {
1153
+ [NETWORK_PRINTER_MANAGER_DEVICE_CONFIG_KEY]: managerDeviceId,
1154
+ [NETWORK_PRINTER_PORT_CONFIG_KEY]: normalizePrinterPort(port),
1155
+ [NETWORK_PRINTER_COLUMNS_CONFIG_KEY]: normalizePrinterColumns(columns),
1156
+ [NETWORK_PRINTER_TRANSPORT_CONFIG_KEY]:
1157
+ String(transport || '').trim() || DEFAULT_NETWORK_PRINTER_TRANSPORT,
1158
+ [NETWORK_PRINTER_CODE_PAGE_CONFIG_KEY]:
1159
+ String(codePage || '').trim() || DEFAULT_NETWORK_PRINTER_CODE_PAGE,
1160
+ };
1161
+
1162
+ setSavingConfig(true);
1163
+
1164
+ try {
1165
+ const savedDeviceConfig = await deviceConfigStore.actions.addDeviceConfigs({
1166
+ device: normalizedHost,
1167
+ people: `/people/${currentCompany.id}`,
1168
+ type: normalizedDeviceType,
1169
+ configs: JSON.stringify(nextConfigs),
1170
+ });
1171
+
1172
+ setDeviceConfigId(String(savedDeviceConfig?.id || deviceConfigId || '').trim());
1173
+ navigation.setParams({
1174
+ configs: nextConfigs,
1175
+ dcId: savedDeviceConfig?.id || deviceConfigId,
1176
+ deviceString: normalizedHost,
1177
+ });
1178
+ runConnectionCheck({
1179
+ hostOverride: normalizedHost,
1180
+ portOverride: isIpCamera
1181
+ ? nextConfigs[NETWORK_CAMERA_PORT_CONFIG_KEY]
1182
+ : nextConfigs[NETWORK_PRINTER_PORT_CONFIG_KEY],
1183
+ });
1184
+ } catch (error) {
1185
+ Alert.alert(
1186
+ deviceProfile.routingAlertTitle,
1187
+ resolveErrorMessage(error),
1188
+ );
1189
+ } finally {
1190
+ setSavingConfig(false);
1191
+ }
1192
+ }, [
1193
+ columns,
1194
+ codePage,
1195
+ currentCompany?.id,
1196
+ deviceHost,
1197
+ deviceConfigStore.actions,
1198
+ deviceConfigId,
1199
+ isIpCamera,
1200
+ managerDeviceId,
1201
+ navigation,
1202
+ password,
1203
+ port,
1204
+ persistedDeviceHost,
1205
+ protocol,
1206
+ streamPath,
1207
+ transport,
1208
+ runConnectionCheck,
1209
+ username,
1210
+ deviceProfile,
1211
+ ]);
1212
+
1213
+ const handleRemoveConfig = useCallback(() => {
1214
+ const normalizedCurrentConfigId = String(deviceConfigId || '').trim();
1215
+ if (!normalizedCurrentConfigId || removingConfig) {
1216
+ return;
1217
+ }
1218
+
1219
+ Alert.alert(
1220
+ deviceProfile.removeConfirmTitle,
1221
+ deviceProfile.removeConfirmMessage,
1222
+ [
1223
+ {
1224
+ text: 'Cancelar',
1225
+ style: 'cancel',
1226
+ },
1227
+ {
1228
+ text: 'Remover',
1229
+ style: 'destructive',
1230
+ onPress: async () => {
1231
+ setRemovingConfig(true);
1232
+
1233
+ try {
1234
+ await deviceConfigStore.actions.remove(normalizedCurrentConfigId);
1235
+ setDeviceConfigId('');
1236
+ navigation.navigate('DevicesIndex');
1237
+ } catch (error) {
1238
+ Alert.alert(
1239
+ deviceProfile.removeConfirmTitle,
1240
+ error?.message || deviceProfile.removeErrorMessage,
1241
+ );
1242
+ } finally {
1243
+ setRemovingConfig(false);
1244
+ }
1245
+ },
1246
+ },
1247
+ ],
1248
+ );
1249
+ }, [
1250
+ deviceConfigId,
1251
+ deviceConfigStore.actions,
1252
+ deviceProfile,
1253
+ navigation,
1254
+ removingConfig,
1255
+ ]);
1256
+
1257
+ const fetchCompanyPrintContext = useCallback(async companyId => {
1258
+ const [configsResponse, companyResponse] = await Promise.all([
1259
+ api
1260
+ .fetch('configs/discovery-configs', {
1261
+ method: 'POST',
1262
+ body: {
1263
+ people: `/people/${companyId}`,
1264
+ },
1265
+ })
1266
+ .catch(() => ({})),
1267
+ api.fetch(`people/${companyId}`).catch(() => currentCompany || {}),
1268
+ ]);
1269
+
1270
+ return {
1271
+ company: {
1272
+ ...(currentCompany || {}),
1273
+ ...(companyResponse || {}),
1274
+ },
1275
+ configs: normalizeConfigEntries(configsResponse),
1276
+ };
1277
+ }, [currentCompany]);
1278
+
1279
+ const fetchLatestClosedOrder = useCallback(async (companyId, channelKey) => {
1280
+ const ordersResponse = await api.fetch('orders', {
1281
+ params: {
1282
+ company: companyId,
1283
+ 'order[id]': 'desc',
1284
+ },
1285
+ });
1286
+ const orders = extractCollectionMembers(ordersResponse);
1287
+ const channel = ORDER_PREVIEW_CHANNELS[channelKey];
1288
+ const expectedApp = normalizePreviewText(channel?.app).toLowerCase();
1289
+
1290
+ return (
1291
+ orders.find(order => {
1292
+ const realStatus = normalizePreviewText(
1293
+ order?.status?.realStatus || order?.status?.real_status,
1294
+ ).toLowerCase();
1295
+ const status = normalizePreviewText(
1296
+ order?.status?.status || order?.status,
1297
+ ).toLowerCase();
1298
+ const orderType = normalizePreviewText(
1299
+ order?.orderType?.orderType || order?.orderType,
1300
+ ).toLowerCase();
1301
+ const app = normalizePreviewText(order?.app).toLowerCase();
1302
+
1303
+ return (
1304
+ (realStatus === 'closed' || status === 'closed') &&
1305
+ (!orderType || orderType === 'sale') &&
1306
+ (!expectedApp || app === expectedApp)
1307
+ );
1308
+ }) ||
1309
+ null
1310
+ );
1311
+ }, []);
1312
+
1313
+ const fetchMarketplaceStateForPreview = useCallback(async (orderId, channelKey) => {
1314
+ const channel = ORDER_PREVIEW_CHANNELS[channelKey];
1315
+ if (!channel?.stateEndpoint || !orderId) {
1316
+ return {};
1317
+ }
1318
+
1319
+ return api.fetch(channel.stateEndpoint(orderId)).catch(() => ({}));
1320
+ }, []);
1321
+
1322
+ const fetchOrderProductsForPreview = useCallback(async order => {
1323
+ const orderId = normalizeEntityId(order);
1324
+ const embeddedOrderProducts = getEmbeddedOrderProducts(order);
1325
+
1326
+ if (
1327
+ hasOrderProducts(embeddedOrderProducts) &&
1328
+ !needsDetailedOrderProductsFetch(embeddedOrderProducts)
1329
+ ) {
1330
+ return embeddedOrderProducts;
1331
+ }
1332
+
1333
+ const orderProductsResponse = await api.fetch('order_products', {
1334
+ params: {
1335
+ order: `/orders/${orderId}`,
1336
+ },
1337
+ });
1338
+ const orderProducts = extractCollectionMembers(orderProductsResponse);
1339
+
1340
+ return orderProducts.length > 0 ? orderProducts : embeddedOrderProducts;
1341
+ }, []);
1342
+
1343
+ const openLatestOrderPreview = useCallback(async channelKey => {
1344
+ const channel = ORDER_PREVIEW_CHANNELS[channelKey];
1345
+ setPrintPreviewVisible(true);
1346
+ setPrintPreviewTitle(`Ultimo pedido ${channel?.label || 'real'}`);
1347
+ setPrintPreviewSubtitle('Pedido completo e vias separadas por fila.');
1348
+ setPrintPreviewDocuments([]);
1349
+ setPrintPreviewError('');
1350
+ setPrintPreviewLogoUrl('');
1351
+ setPrintPreviewLoading(true);
1352
+
1353
+ try {
1354
+ const companyId = currentCompany?.id;
1355
+ const latestOrder = await fetchLatestClosedOrder(companyId, channelKey);
1356
+ const latestOrderId = normalizeEntityId(latestOrder);
1357
+
1358
+ if (!latestOrderId) {
1359
+ throw new Error(
1360
+ `Nenhum pedido ${channel?.label || ''} fechado encontrado para esta empresa.`,
1361
+ );
1362
+ }
1363
+
1364
+ const [orderDetails, companyContext, marketplaceState] = await Promise.all([
1365
+ api.fetch(`orders/${latestOrderId}`).catch(() => latestOrder),
1366
+ fetchCompanyPrintContext(companyId),
1367
+ fetchMarketplaceStateForPreview(latestOrderId, channelKey),
1368
+ ]);
1369
+ const orderProducts = await fetchOrderProductsForPreview(orderDetails);
1370
+ const logoUrl = resolveFileImageUrl(companyContext.company?.image, {
1371
+ company: companyContext.company,
1372
+ });
1373
+
1374
+ setPrintPreviewLogoUrl(logoUrl);
1375
+ setPrintPreviewDocuments(
1376
+ buildOrderDocuments({
1377
+ order: orderDetails || latestOrder,
1378
+ orderProducts,
1379
+ columns,
1380
+ company: companyContext.company,
1381
+ marketplaceState,
1382
+ footerText: companyContext.configs[ORDER_PRINT_FOOTER_TEXT_CONFIG_KEY],
1383
+ }),
1384
+ );
1385
+ } catch (error) {
1386
+ setPrintPreviewError(resolveErrorMessage(error));
1387
+ } finally {
1388
+ setPrintPreviewLoading(false);
1389
+ }
1390
+ }, [
1391
+ columns,
1392
+ currentCompany?.id,
1393
+ fetchCompanyPrintContext,
1394
+ fetchLatestClosedOrder,
1395
+ fetchMarketplaceStateForPreview,
1396
+ fetchOrderProductsForPreview,
1397
+ ]);
1398
+
1399
+ return (
1400
+ <SafeAreaView
1401
+ style={[styles.container, {backgroundColor: brandColors.background}]}>
1402
+ <StateStore stores={['device', 'device_config', 'people']} />
1403
+
1404
+ <ScrollView
1405
+ contentContainerStyle={styles.scroll}
1406
+ showsVerticalScrollIndicator={false}>
1407
+ <View style={styles.heroCard}>
1408
+ <View
1409
+ style={[
1410
+ styles.heroIcon,
1411
+ {backgroundColor: withOpacity(brandColors.primary, 0.12)},
1412
+ ]}>
1413
+ <Icon name={deviceProfile.icon} size={20} color={brandColors.primary} />
1414
+ </View>
1415
+ <View style={styles.heroCopy}>
1416
+ <Text style={styles.heroTitle}>
1417
+ {alias || deviceProfile.detailHeroFallback}
1418
+ </Text>
1419
+ <Text style={styles.heroText}>
1420
+ {getDeviceTypeLabel(normalizedDeviceType)} vinculada ao endereco{' '}
1421
+ {deviceHost || persistedDeviceHost}
1422
+ </Text>
1423
+ </View>
1424
+ </View>
1425
+
1426
+ {loading && (
1427
+ <View style={styles.loadingRow}>
1428
+ <ActivityIndicator size="small" color={brandColors.primary} />
1429
+ <Text style={styles.loadingText}>{deviceProfile.detailLoadingText}</Text>
1430
+ </View>
1431
+ )}
1432
+
1433
+ <View style={styles.sectionCard}>
1434
+ <Text style={styles.sectionTitle}>Status do socket</Text>
1435
+ <Text style={styles.sectionDescription}>
1436
+ {deviceProfile.statusSectionDescription}
1437
+ </Text>
1438
+
1439
+ <View
1440
+ style={[
1441
+ styles.statusCard,
1442
+ {
1443
+ backgroundColor: withOpacity(connectionStatusMeta.color, 0.08),
1444
+ borderColor: withOpacity(connectionStatusMeta.color, 0.28),
1445
+ },
1446
+ ]}>
1447
+ <View style={styles.statusCopy}>
1448
+ <View style={styles.statusTitleRow}>
1449
+ <Icon
1450
+ name={connectionStatusMeta.icon}
1451
+ size={15}
1452
+ color={connectionStatusMeta.color}
1453
+ />
1454
+ <Text
1455
+ style={[
1456
+ styles.statusTitle,
1457
+ {color: connectionStatusMeta.color},
1458
+ ]}>
1459
+ {connectionStatusMeta.label}
1460
+ </Text>
1461
+ </View>
1462
+ <Text style={styles.statusDescription}>{connectionMessage}</Text>
1463
+ </View>
1464
+
1465
+ <TouchableOpacity
1466
+ style={[
1467
+ styles.secondaryButton,
1468
+ checkingConnection && styles.secondaryButtonDisabled,
1469
+ ]}
1470
+ activeOpacity={0.85}
1471
+ disabled={checkingConnection}
1472
+ onPress={() => runConnectionCheck()}>
1473
+ {checkingConnection ? (
1474
+ <ActivityIndicator size="small" color={connectionStatusMeta.color} />
1475
+ ) : (
1476
+ <>
1477
+ <Icon
1478
+ name="refresh-cw"
1479
+ size={14}
1480
+ color={connectionStatusMeta.color}
1481
+ />
1482
+ <Text
1483
+ style={[
1484
+ styles.secondaryButtonText,
1485
+ {color: connectionStatusMeta.color},
1486
+ ]}>
1487
+ Testar
1488
+ </Text>
1489
+ </>
1490
+ )}
1491
+ </TouchableOpacity>
1492
+ </View>
1493
+ </View>
1494
+
1495
+ {!isIpCamera ? (
1496
+ <View style={styles.sectionCard}>
1497
+ <Text style={styles.sectionTitle}>Visualizacao de impressao</Text>
1498
+ <Text style={styles.sectionDescription}>
1499
+ Use dados reais para conferir largura do papel, quebras de linha,
1500
+ origem do pedido e separacao por filas antes de enviar qualquer
1501
+ coisa para a impressora.
1502
+ </Text>
1503
+
1504
+ <View style={styles.actionButtonRow}>
1505
+ <TouchableOpacity
1506
+ style={[
1507
+ styles.primaryButton,
1508
+ styles.actionButton,
1509
+ {backgroundColor: brandColors.primary},
1510
+ ]}
1511
+ activeOpacity={0.85}
1512
+ onPress={() => openLatestOrderPreview('food99')}>
1513
+ <Icon name="eye" size={15} color="#fff" />
1514
+ <Text style={styles.primaryButtonText}>
1515
+ Visualizar ultimo pedido Food99
1516
+ </Text>
1517
+ </TouchableOpacity>
1518
+
1519
+ <TouchableOpacity
1520
+ style={[
1521
+ styles.primaryButton,
1522
+ styles.actionButton,
1523
+ {backgroundColor: brandColors.primary},
1524
+ ]}
1525
+ activeOpacity={0.85}
1526
+ onPress={() => openLatestOrderPreview('ifood')}>
1527
+ <Icon name="eye" size={15} color="#fff" />
1528
+ <Text style={styles.primaryButtonText}>
1529
+ Visualizar ultimo pedido iFood
1530
+ </Text>
1531
+ </TouchableOpacity>
1532
+ </View>
1533
+ </View>
1534
+ ) : null}
1535
+
1536
+ <View style={styles.sectionCard}>
1537
+ <Text style={styles.sectionTitle}>
1538
+ {deviceProfile.registrationSectionTitle}
1539
+ </Text>
1540
+
1541
+ <View style={styles.fieldBlock}>
1542
+ <Text style={styles.fieldLabel}>Nome exibido</Text>
1543
+ <TextInput
1544
+ style={styles.input}
1545
+ value={alias}
1546
+ onChangeText={setAlias}
1547
+ placeholder={deviceProfile.detailAliasPlaceholder}
1548
+ placeholderTextColor="#94A3B8"
1549
+ />
1550
+ </View>
1551
+
1552
+ <View style={styles.fieldBlock}>
1553
+ <Text style={styles.fieldLabel}>IP ou hostname</Text>
1554
+ <TextInput
1555
+ style={styles.input}
1556
+ value={deviceHost}
1557
+ onChangeText={setDeviceHost}
1558
+ placeholder="192.168.0.120"
1559
+ placeholderTextColor="#94A3B8"
1560
+ autoCapitalize="none"
1561
+ autoCorrect={false}
1562
+ />
1563
+ </View>
1564
+
1565
+ <View style={styles.fieldBlock}>
1566
+ <Text style={styles.fieldLabel}>Fabricante</Text>
1567
+ <TextInput
1568
+ style={styles.input}
1569
+ value={manufacturer}
1570
+ onChangeText={setManufacturer}
1571
+ placeholder="Fabricante"
1572
+ placeholderTextColor="#94A3B8"
1573
+ />
1574
+ </View>
1575
+
1576
+ <View style={styles.fieldBlock}>
1577
+ <Text style={styles.fieldLabel}>Modelo</Text>
1578
+ <TextInput
1579
+ style={styles.input}
1580
+ value={model}
1581
+ onChangeText={setModel}
1582
+ placeholder="Modelo"
1583
+ placeholderTextColor="#94A3B8"
1584
+ />
1585
+ </View>
1586
+
1587
+ <View style={styles.fieldBlock}>
1588
+ <Text style={styles.fieldLabel}>Versao/Firmware</Text>
1589
+ <TextInput
1590
+ style={styles.input}
1591
+ value={version}
1592
+ onChangeText={setVersion}
1593
+ placeholder="Versao"
1594
+ placeholderTextColor="#94A3B8"
1595
+ />
1596
+ </View>
1597
+
1598
+ <TouchableOpacity
1599
+ style={[
1600
+ styles.primaryButton,
1601
+ {backgroundColor: brandColors.primary},
1602
+ savingDevice && styles.primaryButtonDisabled,
1603
+ ]}
1604
+ activeOpacity={0.85}
1605
+ disabled={savingDevice}
1606
+ onPress={saveDeviceRegistration}>
1607
+ {savingDevice ? (
1608
+ <ActivityIndicator size="small" color="#fff" />
1609
+ ) : (
1610
+ <>
1611
+ <Icon name="save" size={15} color="#fff" />
1612
+ <Text style={styles.primaryButtonText}>Salvar cadastro</Text>
1613
+ </>
1614
+ )}
1615
+ </TouchableOpacity>
1616
+ </View>
1617
+
1618
+ <View style={styles.sectionCard}>
1619
+ <Text style={styles.sectionTitle}>
1620
+ {deviceProfile.routingSectionTitle}
1621
+ </Text>
1622
+ <Text style={styles.sectionDescription}>
1623
+ {deviceProfile.routingSectionDescription}
1624
+ </Text>
1625
+
1626
+ <View style={styles.inlineFields}>
1627
+ <View style={[styles.fieldBlock, styles.inlineField]}>
1628
+ <Text style={styles.fieldLabel}>
1629
+ {isIpCamera ? 'Porta' : 'Porta TCP'}
1630
+ </Text>
1631
+ <TextInput
1632
+ style={styles.input}
1633
+ value={port}
1634
+ onChangeText={setPort}
1635
+ placeholder={
1636
+ isIpCamera ? '554' : DEFAULT_NETWORK_PRINTER_PORT
1637
+ }
1638
+ placeholderTextColor="#94A3B8"
1639
+ keyboardType="numeric"
1640
+ />
1641
+ </View>
1642
+
1643
+ {isIpCamera ? (
1644
+ <View style={[styles.fieldBlock, styles.inlineField]}>
1645
+ <Text style={styles.fieldLabel}>Protocolo</Text>
1646
+ <View style={styles.pickerWrap}>
1647
+ <Picker
1648
+ selectedValue={protocol || DEFAULT_NETWORK_CAMERA_PROTOCOL}
1649
+ mode={pickerMode}
1650
+ style={styles.picker}
1651
+ dropdownIconColor="#64748B"
1652
+ onValueChange={value =>
1653
+ setProtocol(String(value || DEFAULT_NETWORK_CAMERA_PROTOCOL))
1654
+ }>
1655
+ {NETWORK_CAMERA_PROTOCOL_OPTIONS.map(option => (
1656
+ <Picker.Item
1657
+ key={option.value}
1658
+ label={option.label}
1659
+ value={option.value}
1660
+ />
1661
+ ))}
1662
+ </Picker>
1663
+ </View>
1664
+ </View>
1665
+ ) : (
1666
+ <View style={[styles.fieldBlock, styles.inlineField]}>
1667
+ <Text style={styles.fieldLabel}>Colunas</Text>
1668
+ <TextInput
1669
+ style={styles.input}
1670
+ value={columns}
1671
+ onChangeText={setColumns}
1672
+ placeholder={DEFAULT_NETWORK_PRINTER_COLUMNS}
1673
+ placeholderTextColor="#94A3B8"
1674
+ keyboardType="numeric"
1675
+ />
1676
+ </View>
1677
+ )}
1678
+ </View>
1679
+
1680
+ {isIpCamera ? (
1681
+ <>
1682
+ <View style={styles.fieldBlock}>
1683
+ <Text style={styles.fieldLabel}>Caminho do stream</Text>
1684
+ <TextInput
1685
+ style={styles.input}
1686
+ value={streamPath}
1687
+ onChangeText={setStreamPath}
1688
+ placeholder="Ex.: /Streaming/Channels/101"
1689
+ placeholderTextColor="#94A3B8"
1690
+ autoCapitalize="none"
1691
+ autoCorrect={false}
1692
+ />
1693
+ </View>
1694
+
1695
+ <View style={styles.inlineFields}>
1696
+ <View style={[styles.fieldBlock, styles.inlineField]}>
1697
+ <Text style={styles.fieldLabel}>Usuario</Text>
1698
+ <TextInput
1699
+ style={styles.input}
1700
+ value={username}
1701
+ onChangeText={setUsername}
1702
+ placeholder="Ex.: admin"
1703
+ placeholderTextColor="#94A3B8"
1704
+ autoCapitalize="none"
1705
+ autoCorrect={false}
1706
+ />
1707
+ </View>
1708
+
1709
+ <View style={[styles.fieldBlock, styles.inlineField]}>
1710
+ <Text style={styles.fieldLabel}>Senha</Text>
1711
+ <TextInput
1712
+ style={styles.input}
1713
+ value={password}
1714
+ onChangeText={setPassword}
1715
+ placeholder="Senha da camera"
1716
+ placeholderTextColor="#94A3B8"
1717
+ autoCapitalize="none"
1718
+ autoCorrect={false}
1719
+ secureTextEntry
1720
+ />
1721
+ </View>
1722
+ </View>
1723
+ </>
1724
+ ) : (
1725
+ <>
1726
+ <View style={styles.fieldBlock}>
1727
+ <Text style={styles.fieldLabel}>Transporte</Text>
1728
+ <TextInput
1729
+ style={[styles.input, styles.readonlyInput]}
1730
+ value={transport || DEFAULT_NETWORK_PRINTER_TRANSPORT}
1731
+ editable={false}
1732
+ />
1733
+ </View>
1734
+
1735
+ <View style={styles.fieldBlock}>
1736
+ <Text style={styles.fieldLabel}>Code page</Text>
1737
+ <View style={styles.pickerWrap}>
1738
+ <Picker
1739
+ selectedValue={codePage || DEFAULT_NETWORK_PRINTER_CODE_PAGE}
1740
+ mode={pickerMode}
1741
+ style={styles.picker}
1742
+ dropdownIconColor="#64748B"
1743
+ onValueChange={value =>
1744
+ setCodePage(String(value || DEFAULT_NETWORK_PRINTER_CODE_PAGE))
1745
+ }>
1746
+ {NETWORK_PRINTER_CODE_PAGE_OPTIONS.map(option => (
1747
+ <Picker.Item
1748
+ key={option.value}
1749
+ label={option.label}
1750
+ value={option.value}
1751
+ />
1752
+ ))}
1753
+ </Picker>
1754
+ </View>
1755
+ </View>
1756
+ </>
1757
+ )}
1758
+
1759
+ <View style={styles.fieldBlock}>
1760
+ <Text style={styles.fieldLabel}>Device responsavel</Text>
1761
+ <View style={styles.pickerWrap}>
1762
+ <Picker
1763
+ selectedValue={managerDeviceId || ''}
1764
+ mode={pickerMode}
1765
+ style={styles.picker}
1766
+ dropdownIconColor="#64748B"
1767
+ onValueChange={value =>
1768
+ setManagerDeviceId(String(value || '').trim())
1769
+ }>
1770
+ <Picker.Item
1771
+ label="Selecione um PDV ou DISPLAY"
1772
+ value=""
1773
+ />
1774
+ {managerDeviceOptions.map(option => (
1775
+ <Picker.Item
1776
+ key={option.deviceId}
1777
+ label={option.label}
1778
+ value={option.deviceId}
1779
+ />
1780
+ ))}
1781
+ </Picker>
1782
+ </View>
1783
+ </View>
1784
+
1785
+ <TouchableOpacity
1786
+ style={[
1787
+ styles.primaryButton,
1788
+ {backgroundColor: brandColors.primary},
1789
+ savingConfig && styles.primaryButtonDisabled,
1790
+ ]}
1791
+ activeOpacity={0.85}
1792
+ disabled={savingConfig}
1793
+ onPress={savePrinterConfig}>
1794
+ {savingConfig ? (
1795
+ <ActivityIndicator size="small" color="#fff" />
1796
+ ) : (
1797
+ <>
1798
+ <Icon name="save" size={15} color="#fff" />
1799
+ <Text style={styles.primaryButtonText}>
1800
+ {isIpCamera
1801
+ ? 'Salvar acesso da camera'
1802
+ : 'Salvar roteamento'}
1803
+ </Text>
1804
+ </>
1805
+ )}
1806
+ </TouchableOpacity>
1807
+ </View>
1808
+
1809
+ <View style={styles.sectionCard}>
1810
+ <Text style={styles.sectionTitle}>Rodapé do sistema</Text>
1811
+ <Text style={styles.sectionDescription}>
1812
+ {deviceProfile.footerDebugDescription}
1813
+ </Text>
1814
+
1815
+ <TouchableOpacity
1816
+ style={[
1817
+ styles.toggleRow,
1818
+ deviceRuntimeDebugInfoEnabled && styles.toggleRowActive,
1819
+ ]}
1820
+ activeOpacity={0.85}
1821
+ onPress={() =>
1822
+ setDeviceRuntimeDebugInfoEnabled(currentValue => !currentValue)
1823
+ }>
1824
+ <View>
1825
+ <Text style={styles.toggleRowLabel}>Exibir debug detalhado</Text>
1826
+ <Text style={styles.toggleRowValue}>
1827
+ {deviceRuntimeDebugInfoEnabled ? 'Ativo' : 'Inativo'}
1828
+ </Text>
1829
+ </View>
1830
+ <Icon
1831
+ name={
1832
+ deviceRuntimeDebugInfoEnabled ? 'toggle-right' : 'toggle-left'
1833
+ }
1834
+ size={28}
1835
+ color={deviceRuntimeDebugInfoEnabled ? '#10b981' : '#94A3B8'}
1836
+ />
1837
+ </TouchableOpacity>
1838
+
1839
+ <TouchableOpacity
1840
+ style={[
1841
+ styles.primaryButton,
1842
+ {backgroundColor: brandColors.primary},
1843
+ savingRuntimeDebugInfo && styles.primaryButtonDisabled,
1844
+ ]}
1845
+ activeOpacity={0.85}
1846
+ disabled={savingRuntimeDebugInfo}
1847
+ onPress={saveRuntimeDebugInfoConfig}>
1848
+ {savingRuntimeDebugInfo ? (
1849
+ <ActivityIndicator size="small" color="#fff" />
1850
+ ) : (
1851
+ <>
1852
+ <Icon name="save" size={15} color="#fff" />
1853
+ <Text style={styles.primaryButtonText}>
1854
+ Salvar debug do rodape
1855
+ </Text>
1856
+ </>
1857
+ )}
1858
+ </TouchableOpacity>
1859
+ </View>
1860
+
1861
+ {deviceConfigId ? (
1862
+ <View style={styles.sectionCard}>
1863
+ <Text style={styles.sectionTitle}>
1864
+ {deviceProfile.removeSectionTitle}
1865
+ </Text>
1866
+ <Text style={styles.sectionDescription}>
1867
+ {deviceProfile.removeSectionDescription}
1868
+ </Text>
1869
+
1870
+ <TouchableOpacity
1871
+ style={[
1872
+ styles.primaryButton,
1873
+ {backgroundColor: '#DC2626'},
1874
+ removingConfig && styles.primaryButtonDisabled,
1875
+ ]}
1876
+ activeOpacity={0.85}
1877
+ disabled={removingConfig}
1878
+ onPress={handleRemoveConfig}>
1879
+ {removingConfig ? (
1880
+ <ActivityIndicator size="small" color="#fff" />
1881
+ ) : (
1882
+ <>
1883
+ <Icon name="trash-2" size={15} color="#fff" />
1884
+ <Text style={styles.primaryButtonText}>
1885
+ {deviceProfile.removeButtonLabel}
1886
+ </Text>
1887
+ </>
1888
+ )}
1889
+ </TouchableOpacity>
1890
+ </View>
1891
+ ) : null}
1892
+ </ScrollView>
1893
+
1894
+ {!isIpCamera ? (
1895
+ <NetworkPrinterPreviewModal
1896
+ visible={printPreviewVisible}
1897
+ onClose={() => setPrintPreviewVisible(false)}
1898
+ codePage={codePage}
1899
+ columns={columns}
1900
+ documents={printPreviewDocuments}
1901
+ error={printPreviewError}
1902
+ logoUrl={printPreviewLogoUrl}
1903
+ loading={printPreviewLoading}
1904
+ printerManufacturer={manufacturer}
1905
+ printerModel={model}
1906
+ subtitle={printPreviewSubtitle}
1907
+ title={printPreviewTitle}
1908
+ transport={transport}
1909
+ />
1910
+ ) : null}
1911
+ </SafeAreaView>
1912
+ );
1913
+ };
1914
+
1915
+ export default PrinterDeviceDetailPage;
1916
+ // TODO(store-first): quando este arquivo for mexido, mover a leitura para stores, remover api.fetch e evitar repassar dados em objetos quando o store ja resolver isso.