@controleonline/ui-common 1.2.70 → 1.2.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) 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 +32 -28
  11. package/src/api/fetch.js +1 -1
  12. package/src/api/index.js +328 -58
  13. package/src/react/components/AddImportModal.js +95 -111
  14. package/src/react/components/AnimatedModal.js +171 -0
  15. package/src/react/components/AnimatedModal.styles.js +21 -0
  16. package/src/react/components/AppTypeSwitcher.js +164 -0
  17. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  18. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  19. package/src/react/components/BottomNavigationBar.config.js +211 -231
  20. package/src/react/components/BottomNavigationBar.js +86 -31
  21. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  22. package/src/react/components/CategoryForm.js +52 -35
  23. package/src/react/components/DefaultProvider.native.js +194 -156
  24. package/src/react/components/DefaultProvider.web.js +188 -112
  25. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  26. package/src/react/components/DeviceAlertSoundService.js +3 -3
  27. package/src/react/components/KioskModeBridge.js +2 -2
  28. package/src/react/components/LauncherModeBridge.js +2 -2
  29. package/src/react/components/ManagerPushBridge.native.js +2 -2
  30. package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
  31. package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
  32. package/src/react/components/RemoteCheckoutService.js +28 -20
  33. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  34. package/src/react/components/RuntimeInfoFooter.js +137 -45
  35. package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
  36. package/src/react/components/StateStore.js +258 -0
  37. package/src/react/components/UnifiedPaymentBar.js +7 -4
  38. package/src/react/components/UserAvatar.js +160 -0
  39. package/src/react/components/WebsocketListener.native.js +11 -11
  40. package/src/react/components/integrations/IntegrationHero/index.js +48 -0
  41. package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
  42. package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
  43. package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
  44. package/src/react/config/deviceConfigBootstrap.js +71 -30
  45. package/src/react/css/orders.js +72 -0
  46. package/src/react/pages/Connections.js +229 -0
  47. package/src/react/pages/Connections.styles.js +222 -0
  48. package/src/react/pages/DeviceDetailPage.js +2814 -0
  49. package/src/react/pages/DeviceDetailPage.styles.js +308 -0
  50. package/src/react/pages/Devices/currentDevice.js +194 -0
  51. package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
  52. package/src/react/pages/Devices/deviceTypes/all.js +23 -0
  53. package/src/react/pages/Devices/deviceTypes/device.js +25 -0
  54. package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
  55. package/src/react/pages/Devices/deviceTypes/display.js +26 -0
  56. package/src/react/pages/Devices/deviceTypes/index.js +145 -0
  57. package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
  58. package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
  59. package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
  60. package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
  61. package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
  62. package/src/react/pages/Devices.js +183 -0
  63. package/src/react/pages/Devices.styles.js +353 -0
  64. package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
  65. package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
  66. package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
  67. package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
  68. package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
  69. package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
  70. package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
  71. package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
  72. package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
  73. package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
  74. package/src/react/pages/Food99IntegrationPage.js +1 -0
  75. package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
  76. package/src/react/pages/GenericLogPage.js +42 -167
  77. package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
  78. package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
  79. package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
  80. package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
  81. package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
  82. package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
  83. package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
  84. package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
  85. package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
  86. package/src/react/pages/IFoodIntegrationPage.js +1 -0
  87. package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
  88. package/src/react/pages/Imports.js +214 -166
  89. package/src/react/pages/IntegrationConfigPage.js +744 -0
  90. package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
  91. package/src/react/pages/Integrations.js +299 -0
  92. package/src/react/pages/Integrations.styles.js +130 -0
  93. package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
  94. package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
  95. package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
  96. package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
  97. package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
  98. package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
  99. package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
  100. package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
  101. package/src/react/pages/PrinterDeviceFormPage.js +521 -0
  102. package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
  103. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  104. package/src/react/pages/SettingsPage/index.js +1266 -1167
  105. package/src/react/pages/WhatsAppConnectionPage.js +459 -0
  106. package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
  107. package/src/react/pages/integrationsCatalog.js +399 -0
  108. package/src/react/print/providers/local.js +1 -1
  109. package/src/react/router/publicRoutes.js +25 -0
  110. package/src/react/services/Cielo/Checkout.js +39 -0
  111. package/src/react/services/Cielo/Cielo.js +193 -0
  112. package/src/react/services/Cielo/Print.js +7 -0
  113. package/src/react/services/InfinitePay/Checkout.js +33 -0
  114. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  115. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  116. package/src/react/styles/global.js +115 -0
  117. package/src/react/utils/categoryContexts.js +46 -0
  118. package/src/react/utils/entityDisplay.js +222 -217
  119. package/src/react/utils/fileUrl.js +201 -16
  120. package/src/react/utils/integrationPage.js +16 -0
  121. package/src/react/utils/menuNavigation.js +31 -0
  122. package/src/react/utils/orderIdentity.js +277 -0
  123. package/src/react/utils/remotePayment.js +115 -61
  124. package/src/react/utils/runtimeFooter.js +40 -22
  125. package/src/react/utils/runtimeLanguage.js +50 -9
  126. package/src/react/utils/runtimeMenu.js +190 -98
  127. package/src/react/utils/runtimeZoom.js +43 -0
  128. package/src/react/utils/shopConfig.js +72 -34
  129. package/src/react/utils/shopFranchises.js +182 -23
  130. package/src/react/utils/socketRuntimePipeline.js +14 -14
  131. package/src/react/utils/storeColumns.js +23 -4
  132. package/src/react/utils/userAvatar.js +104 -0
  133. package/src/store/categories/index.js +48 -20
  134. package/src/store/configs/index.js +2 -2
  135. package/src/store/device_config/index.js +62 -21
  136. package/src/store/entity_log/index.js +22 -1
  137. package/src/store/imports/customActions.js +51 -0
  138. package/src/store/imports/index.js +89 -88
  139. package/src/store/status/index.js +11 -11
  140. package/src/store/themes/index.js +22 -0
  141. package/src/store/timezones/index.js +3 -1
  142. package/src/tests/browser/manager/connections.spec.js +143 -0
  143. package/src/tests/browser/manager/devices-current.spec.js +343 -0
  144. package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
  145. package/src/tests/react/api/deviceHeaders.test.js +114 -0
  146. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  147. package/src/tests/react/components/AddImportModal.test.js +166 -0
  148. package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
  149. package/src/tests/react/components/UserAvatar.test.js +52 -0
  150. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  151. package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
  152. package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
  153. package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
  154. package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
  155. package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
  156. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  157. package/src/tests/react/services/Cielo.test.js +190 -0
  158. package/src/tests/react/store/categoriesOrdering.test.js +26 -0
  159. package/src/tests/react/utils/appDomain.test.js +62 -0
  160. package/src/tests/react/utils/entityDisplay.test.js +2 -2
  161. package/src/tests/react/utils/fileUrl.test.js +62 -0
  162. package/src/tests/react/utils/formatter.test.mjs +33 -0
  163. package/src/tests/react/utils/importStatus.test.js +2 -2
  164. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  165. package/src/tests/react/utils/remotePayment.test.js +62 -0
  166. package/src/tests/react/utils/runtimeFooter.test.js +32 -9
  167. package/src/tests/react/utils/runtimeLanguage.test.js +80 -10
  168. package/src/tests/react/utils/runtimeMenu.test.js +195 -111
  169. package/src/tests/react/utils/runtimeZoom.test.js +38 -0
  170. package/src/tests/react/utils/shopConfig.test.js +75 -0
  171. package/src/tests/react/utils/shopFranchises.test.js +122 -12
  172. package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
  173. package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
  174. package/src/tests/react/utils/translate.test.mjs +252 -396
  175. package/src/tests/react/utils/userAvatar.test.js +63 -0
  176. package/src/utils/appDomain.js +15 -4
  177. package/src/utils/formatter.js +122 -16
  178. package/src/utils/integrationConfigs.js +1 -0
  179. package/src/utils/translate.js +277 -398
  180. package/src/utils/translateDiscovery.js +96 -0
  181. package/src/utils/translateNormalize.js +33 -0
  182. package/src/utils/translatePending.js +92 -0
  183. package/src/utils/translateStorage.js +142 -0
  184. package/.github/agents/developer.agent.md +0 -30
  185. package/.github/agents/devops.agent.md +0 -30
  186. package/.github/agents/qa.agent.md +0 -30
  187. package/.github/agents/security.agent.md +0 -30
  188. package/.github/workflows/npmjs.yml +0 -35
@@ -0,0 +1,2814 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { Alert, FlatList, Platform, Pressable, ScrollView, Switch, Text, TextInput, TouchableOpacity, View } from 'react-native';
3
+ import { SafeAreaView } from 'react-native-safe-area-context';
4
+ import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
5
+ import {Picker} from '@react-native-picker/picker';
6
+ import { useStore } from '@store';
7
+ import { api } from '@controleonline/ui-common/src/api';
8
+ import Formatter from '@controleonline/ui-common/src/utils/formatter';
9
+ import StateStore from '@controleonline/ui-common/src/react/components/StateStore';
10
+ import PaymentTypesByWalletTab from '@controleonline/ui-common/src/react/pages/SettingsPage/PaymentTypesByWalletTab';
11
+ import { appendScreenMetrics } from '@controleonline/ui-common/src/react/utils/screenMetrics';
12
+ import { useMessage } from '@controleonline/ui-common/src/react/components/MessageService';
13
+ import { resolveThemePalette, withOpacity } from '@controleonline/../../src/styles/branding';
14
+ import { colors } from '@controleonline/../../src/styles/colors';
15
+ import Icon from 'react-native-vector-icons/Feather';
16
+ import styles from './DeviceDetailPage.styles';
17
+ import packageJson from '@package';
18
+ import DefaultTooltip from '@controleonline/ui-default/src/react/components/help/DefaultTooltip';
19
+
20
+ import {
21
+ canDisplayChangePrinter,
22
+ DEVICE_ANDROID_KIOSK_ENABLED_CONFIG_KEY,
23
+ DEVICE_ANDROID_LAUNCHER_ENABLED_CONFIG_KEY,
24
+ DISPLAY_AUTO_PRINT_PRODUCT_CONFIG_KEY,
25
+ DISPLAY_ALLOW_PRINTER_CHANGE_CONFIG_KEY,
26
+ DEVICE_ALERT_SOUND_ENABLED_KEY,
27
+ DEVICE_ALERT_SOUND_URL_KEY,
28
+ DEVICE_ORDER_VISIBILITY_COMPANY,
29
+ DEVICE_ORDER_VISIBILITY_DEVICE,
30
+ DEVICE_ORDER_VISIBILITY_KEY,
31
+ DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY,
32
+ isPosAutoPrintEnabled,
33
+ isPosCashRegisterOpen,
34
+ isTruthyValue,
35
+ parseConfigsObject,
36
+ POS_AUTO_PRINT_ENABLED_CONFIG_KEY,
37
+ POS_CASH_MANAGEMENT_MODE_CASH_REGISTER,
38
+ POS_CASH_MANAGEMENT_MODE_CONFIG_KEY,
39
+ POS_CASH_MANAGEMENT_MODE_DAILY,
40
+ POS_CHECK_ORDER_MANAGEMENT_MODE_CONFIG_KEY,
41
+ POS_CHECK_ORDER_MANAGEMENT_MODE_EXISTING_ONLY,
42
+ POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE,
43
+ POS_CHECK_ORDER_TYPE_CONFIG_KEY,
44
+ POS_CHECK_ORDER_TYPE_NONE,
45
+ POS_CHECK_ORDER_TYPE_TAB,
46
+ POS_CHECK_ORDER_TYPE_TABLE,
47
+ POS_CHECK_ORDER_TYPE_STAMP,
48
+ POS_DELIVERY_ENABLED_CONFIG_KEY,
49
+ POS_OPERATION_MODE_COUNTER,
50
+ POS_OPERATION_MODE_CONFIG_KEY,
51
+ POS_OPERATION_MODE_OPTIONS,
52
+ POS_PRODUCT_SHOWCASE_CONFIG_KEY,
53
+ POS_PRINT_MODE_FORM,
54
+ POS_PRINT_MODE_ORDER,
55
+ getPosOperationModeOption,
56
+ isAndroidKioskEnabled,
57
+ isAndroidLauncherEnabled,
58
+ isPosDeliveryEnabled,
59
+ resolvePosCheckOrderManagementMode,
60
+ resolvePosCheckOrderType,
61
+ resolvePosCheckOrderTypeForShop,
62
+ resolveDeviceOrderVisibility,
63
+ resolvePosCashManagementMode,
64
+ resolvePosOperationMode,
65
+ resolvePosPrintMode,
66
+ } from '@controleonline/ui-common/src/react/config/deviceConfigBootstrap';
67
+
68
+ import {
69
+ filterDeviceConfigsByCompany,
70
+ getCompanyPaymentDeviceOptions,
71
+ getPaymentGatewayFromConfigs,
72
+ getPaymentGatewayLabel,
73
+ isPaymentCapableDeviceConfig,
74
+ isPdvPrinterEnabled,
75
+ normalizeDeviceId,
76
+ normalizeEntityId,
77
+ PAYMENT_TYPE_IDS_CONFIG_KEY,
78
+ PAYMENT_GATEWAY_CIELO,
79
+ PDV_PRINTER_ENABLED_CONFIG_KEY,
80
+ ORDER_PAYMENT_DEVICE_CONFIG_KEY,
81
+ POS_GATEWAY_CONFIG_KEY,
82
+ } from '@controleonline/ui-common/src/react/utils/paymentDevices';
83
+ import {
84
+ normalizeBooleanConfig,
85
+ SHOP_LOYALTY_COUPONS_ENABLED_CONFIG_KEY,
86
+ } from '@controleonline/ui-common/src/react/utils/shopConfig';
87
+
88
+ import {
89
+ getPrinterOptionValue,
90
+ getDeviceTypeLabel,
91
+ getPrinterLabel,
92
+ getPrinterOptions,
93
+ } from '@controleonline/ui-common/src/react/utils/printerDevices';
94
+
95
+ import { inlineStyle_667_12, inlineStyle_1301_61 } from './DeviceDetailPage.styles';
96
+
97
+ const hex = {
98
+ success: '#10b981',
99
+ danger: '#c10015',
100
+ warning: '#e67e22',
101
+ info: '#0EA5E9',
102
+ purple: '#8B5CF6',
103
+ };
104
+
105
+ const PAYMENT_ICONS = {
106
+ dinheiro: 'dollar-sign',
107
+ pix: 'zap',
108
+ debito: 'credit-card',
109
+ credito: 'credit-card',
110
+ default: 'hash',
111
+ };
112
+
113
+ const paymentIcon = label => {
114
+ const l = String(label || '').toLowerCase();
115
+ if (l.includes('pix')) return PAYMENT_ICONS.pix;
116
+ if (l.includes('debit')) return PAYMENT_ICONS.debito;
117
+ if (l.includes('crédit') || l.includes('credit')) return PAYMENT_ICONS.credito;
118
+ if (l.includes('dinh')) return PAYMENT_ICONS.dinheiro;
119
+ return PAYMENT_ICONS.default;
120
+ };
121
+
122
+ const DISPLAY_DEVICE_TYPE = 'DISPLAY';
123
+ const PDV_DEVICE_TYPE = 'PDV';
124
+ const DISPLAY_DEVICE_LINK_CONFIG_KEY = 'display-id';
125
+ const DISPLAY_DEVICE_PRINTER_CONFIG_KEY = 'printer';
126
+ const tt = (type, key) => global.t?.t('configs', type, key);
127
+
128
+ const PDV_TAB_OPERATION = 'operation';
129
+ const PDV_TAB_ORDERS = 'orders';
130
+ const PDV_TAB_DEVICE = 'device';
131
+ const PDV_TAB_PAYMENT_TYPES = 'payment-types';
132
+ const PDV_TAB_MOVEMENT = 'movement';
133
+
134
+ const PDV_DETAIL_TABS = [
135
+ {key: PDV_TAB_OPERATION, icon: 'sliders', labelKey: 'pdvOperation'},
136
+ {key: PDV_TAB_ORDERS, icon: 'list', labelKey: 'pdvOrders'},
137
+ {key: PDV_TAB_DEVICE, icon: 'cpu', labelKey: 'pdvDevice'},
138
+ {key: PDV_TAB_PAYMENT_TYPES, icon: 'credit-card', labelKey: 'pdvPayments'},
139
+ {key: PDV_TAB_MOVEMENT, icon: 'bar-chart-2', labelKey: 'pdvMovement'},
140
+ ];
141
+
142
+ const formatApiError = (error, fallback) => {
143
+ if (typeof error === 'string') {
144
+ return error.trim() || fallback;
145
+ }
146
+
147
+ if (Array.isArray(error?.message)) {
148
+ return (
149
+ error.message
150
+ .map(item => item?.message || item?.title || String(item || '').trim())
151
+ .filter(Boolean)
152
+ .join('\n') || fallback
153
+ );
154
+ }
155
+
156
+ return error?.message || error?.description || fallback;
157
+ };
158
+
159
+ const getDisplayLabel = display => {
160
+ const name = String(display?.display || '').trim();
161
+ const type = String(display?.displayType || '').trim().toUpperCase();
162
+
163
+ if (name && type) {
164
+ return `${name} (${type})`;
165
+ }
166
+
167
+ if (name) {
168
+ return name;
169
+ }
170
+
171
+ return `Display #${normalizeEntityId(display) || '--'}`;
172
+ };
173
+
174
+ const getProductShowcaseLabel = showcase => {
175
+ const name = String(showcase?.name || '').trim();
176
+ const externalCode = String(showcase?.externalStoreCode || '').trim();
177
+ if (name && externalCode) return `${name} (${externalCode})`;
178
+ return name || `Vitrine #${normalizeEntityId(showcase) || '--'}`;
179
+ };
180
+
181
+ const getIsOpen = configs => {
182
+ return isPosCashRegisterOpen(configs);
183
+ };
184
+
185
+ const confirm = (msg, cb) => {
186
+ if (Platform.OS === 'web') {
187
+ if (window.confirm(msg)) cb();
188
+ } else {
189
+ Alert.alert('Confirmação', msg, [
190
+ { text: 'Cancelar', style: 'cancel' },
191
+ { text: 'Confirmar', onPress: cb },
192
+ ]);
193
+ }
194
+ };
195
+
196
+ const getDeviceSwitchProps = ({
197
+ disabled = false,
198
+ palette,
199
+ value = false,
200
+ }) => {
201
+ const offTrackColor = disabled
202
+ ? palette.switchDisabledTrack
203
+ : palette.switchOffTrack;
204
+ const onTrackColor = disabled
205
+ ? palette.switchDisabledTrack
206
+ : palette.switchOnTrack;
207
+
208
+ return {
209
+ ios_backgroundColor: offTrackColor,
210
+ thumbColor: disabled
211
+ ? palette.switchDisabledThumb
212
+ : value
213
+ ? palette.switchOnThumb
214
+ : palette.switchOffThumb,
215
+ trackColor: {
216
+ false: offTrackColor,
217
+ true: onTrackColor,
218
+ },
219
+ };
220
+ };
221
+
222
+ const OptionButtonChip = ({
223
+ label,
224
+ selected,
225
+ disabled,
226
+ colors: optionColors,
227
+ tooltip,
228
+ onPress,
229
+ }) => {
230
+ const [hovered, setHovered] = useState(false);
231
+ const showTooltip = Platform.OS === 'web' && disabled && Boolean(tooltip) && hovered;
232
+
233
+ return (
234
+ <Pressable
235
+ onHoverIn={() => setHovered(true)}
236
+ onHoverOut={() => setHovered(false)}
237
+ style={[styles.optionButtonHoverWrap, hovered && styles.optionButtonHoverWrapActive]}
238
+ >
239
+ <TouchableOpacity
240
+ style={[
241
+ styles.optionButton,
242
+ selected && styles.optionButtonActive,
243
+ optionColors && {
244
+ backgroundColor: selected
245
+ ? optionColors.buttonBackground
246
+ : optionColors.buttonBackgroundSecondary,
247
+ borderColor: selected
248
+ ? optionColors.buttonBorder
249
+ : optionColors.buttonBorderSecondary,
250
+ },
251
+ disabled && {opacity: 0.55},
252
+ ]}
253
+ accessibilityHint={tooltip || undefined}
254
+ activeOpacity={disabled ? 1 : 0.85}
255
+ disabled={disabled}
256
+ onPress={disabled ? undefined : onPress}
257
+ >
258
+ <Text
259
+ style={[
260
+ styles.optionButtonText,
261
+ selected && styles.optionButtonTextActive,
262
+ optionColors && {
263
+ color: selected
264
+ ? optionColors.buttonText
265
+ : optionColors.buttonTextSecondary,
266
+ },
267
+ ]}
268
+ >
269
+ {label}
270
+ </Text>
271
+ </TouchableOpacity>
272
+
273
+ {showTooltip ? (
274
+ <View pointerEvents="none" style={styles.optionButtonTooltip}>
275
+ <Text style={styles.optionButtonTooltipText}>{tooltip}</Text>
276
+ </View>
277
+ ) : null}
278
+ </Pressable>
279
+ );
280
+ };
281
+
282
+ const DeviceDetailPage = () => {
283
+ const route = useRoute();
284
+ const navigation = useNavigation();
285
+ const {
286
+ deviceId: routeDeviceId,
287
+ } = route.params || {};
288
+ const deviceId = useMemo(
289
+ () => normalizeEntityId(routeDeviceId),
290
+ [routeDeviceId],
291
+ );
292
+
293
+ const invoiceStore = useStore('invoice');
294
+ const deviceConfigStore = useStore('device_config');
295
+ const deviceStore = useStore('device');
296
+ const displayStore = useStore('displays');
297
+ const peopleStore = useStore('people');
298
+ const printerStore = useStore('printer');
299
+ const themeStore = useStore('theme');
300
+ const websocketStore = useStore('websocket');
301
+ const messageApi = useMessage() || {};
302
+
303
+ const { currentCompany } = peopleStore.getters;
304
+ const { item: runtimeDevice } = deviceStore.getters;
305
+ const { item: runtimeDeviceConfig } = deviceConfigStore.getters;
306
+ const { items: displays = [], isLoading: isLoadingDisplays } = displayStore.getters;
307
+ const { items: printers = [], isLoading: isLoadingPrinters } = printerStore.getters;
308
+ const { colors: themeColors } = themeStore.getters;
309
+ const websocketActions = websocketStore.actions;
310
+ const runtimeCompanyConfigs = useMemo(
311
+ () => parseConfigsObject(currentCompany?.configs),
312
+ [currentCompany?.configs],
313
+ );
314
+ const showSystemError = useCallback(
315
+ (error, fallback) => {
316
+ messageApi.showError?.(formatApiError(error, fallback));
317
+ },
318
+ [messageApi],
319
+ );
320
+ const loyaltyCouponsEnabled = useMemo(
321
+ () => {
322
+ const hasLoyaltyCouponsEnabledKey = Object.prototype.hasOwnProperty.call(
323
+ runtimeCompanyConfigs || {},
324
+ SHOP_LOYALTY_COUPONS_ENABLED_CONFIG_KEY,
325
+ );
326
+
327
+ return hasLoyaltyCouponsEnabledKey
328
+ ? normalizeBooleanConfig(
329
+ runtimeCompanyConfigs?.[SHOP_LOYALTY_COUPONS_ENABLED_CONFIG_KEY],
330
+ )
331
+ : true;
332
+ },
333
+ [runtimeCompanyConfigs],
334
+ );
335
+
336
+ const currentDevice =
337
+ deviceId &&
338
+ normalizeEntityId(runtimeDevice?.id || runtimeDevice?.['@id']) === deviceId
339
+ ? runtimeDevice
340
+ : {};
341
+ const currentDeviceConfig =
342
+ deviceId &&
343
+ normalizeEntityId(
344
+ runtimeDeviceConfig?.device?.id ||
345
+ runtimeDeviceConfig?.device?.['@id'] ||
346
+ runtimeDeviceConfig?.deviceId ||
347
+ runtimeDeviceConfig?.device?.deviceId,
348
+ ) === deviceId
349
+ ? runtimeDeviceConfig
350
+ : {};
351
+ const deviceString = String(
352
+ currentDevice?.device || currentDeviceConfig?.device?.device || '',
353
+ ).trim();
354
+ const deviceType = String(
355
+ currentDevice?.type ||
356
+ currentDevice?.deviceType ||
357
+ currentDeviceConfig?.type ||
358
+ currentDeviceConfig?.device?.type ||
359
+ '',
360
+ )
361
+ .trim()
362
+ .toUpperCase();
363
+ const normalizedInitialConfigs = useMemo(
364
+ () => parseConfigsObject(currentDeviceConfig?.configs),
365
+ [currentDeviceConfig?.configs],
366
+ );
367
+ const initialAlias = String(
368
+ currentDevice?.alias || currentDeviceConfig?.device?.alias || currentDevice?.device || '',
369
+ ).trim();
370
+ const isDisplayDevice = deviceType === DISPLAY_DEVICE_TYPE;
371
+ const isPdvDevice = deviceType === PDV_DEVICE_TYPE;
372
+
373
+ const actionsRef = useRef({});
374
+ const stampAutoDisableSignatureRef = useRef('');
375
+ actionsRef.current = {
376
+ invoiceActions: invoiceStore.actions,
377
+ deviceConfigActions: deviceConfigStore.actions,
378
+ deviceActions: deviceStore.actions,
379
+ };
380
+
381
+ const brandColors = useMemo(
382
+ () => resolveThemePalette({ ...themeColors, ...(currentCompany?.theme?.colors || {}) }, colors),
383
+ [themeColors, currentCompany?.id],
384
+ );
385
+
386
+ const [products, setProducts] = useState([]);
387
+ const [productShowcases, setProductShowcases] = useState([]);
388
+ const [companyDeviceConfigs, setCompanyDeviceConfigs] = useState([]);
389
+ const [inflowData, setInflowData] = useState(null);
390
+ const [configs, setConfigs] = useState(normalizedInitialConfigs || {});
391
+ const [loadingConfigData, setLoadingConfigData] = useState(false);
392
+ const [loadingCompanyDeviceConfigs, setLoadingCompanyDeviceConfigs] =
393
+ useState(false);
394
+ const [loadingMovementData, setLoadingMovementData] = useState(false);
395
+ const [actionLoading, setActionLoading] = useState(false);
396
+ const [activePdvTab, setActivePdvTab] = useState(PDV_TAB_OPERATION);
397
+ const [savingPaymentTarget, setSavingPaymentTarget] = useState(false);
398
+ const [savingPdvSettings, setSavingPdvSettings] = useState(false);
399
+ const [savingPaymentTypes, setSavingPaymentTypes] = useState(false);
400
+ const [savingPosOperationMode, setSavingPosOperationMode] = useState(false);
401
+ const [savingProductShowcase, setSavingProductShowcase] = useState(false);
402
+ const [savingLauncherMode, setSavingLauncherMode] = useState(false);
403
+ const [savingAlertSound, setSavingAlertSound] = useState(false);
404
+ const [savingOrderVisibility, setSavingOrderVisibility] = useState(false);
405
+ const [savingDeviceDeliverySettings, setSavingDeviceDeliverySettings] =
406
+ useState(false);
407
+ const [savingRuntimeDebugInfo, setSavingRuntimeDebugInfo] = useState(false);
408
+ const [sendingCatalogRefresh, setSendingCatalogRefresh] = useState(false);
409
+ const [loadingProductShowcases, setLoadingProductShowcases] = useState(false);
410
+ const [search, setSearch] = useState('');
411
+ const [devicePaymentTarget, setDevicePaymentTarget] = useState(
412
+ normalizeDeviceId(normalizedInitialConfigs?.[ORDER_PAYMENT_DEVICE_CONFIG_KEY]),
413
+ );
414
+ const [pdvGateway, setPdvGateway] = useState(
415
+ getPaymentGatewayFromConfigs(normalizedInitialConfigs),
416
+ );
417
+ const [pdvPrinterEnabled, setPdvPrinterEnabled] = useState(
418
+ isPdvPrinterEnabled(normalizedInitialConfigs),
419
+ );
420
+ const [posOperationMode, setPosOperationMode] = useState(
421
+ resolvePosOperationMode(normalizedInitialConfigs),
422
+ );
423
+ const [productShowcaseId, setProductShowcaseId] = useState(
424
+ normalizeEntityId(normalizedInitialConfigs?.[POS_PRODUCT_SHOWCASE_CONFIG_KEY]),
425
+ );
426
+ const [androidKioskEnabled, setAndroidKioskEnabled] = useState(
427
+ isAndroidKioskEnabled(normalizedInitialConfigs),
428
+ );
429
+ const [androidLauncherEnabled, setAndroidLauncherEnabled] = useState(
430
+ isAndroidLauncherEnabled(normalizedInitialConfigs),
431
+ );
432
+ const [counterAutoPrintEnabled, setCounterAutoPrintEnabled] = useState(
433
+ isPosAutoPrintEnabled(normalizedInitialConfigs),
434
+ );
435
+ const [counterPrintMode, setCounterPrintMode] = useState(
436
+ resolvePosPrintMode(normalizedInitialConfigs),
437
+ );
438
+ const [counterCashManagementMode, setCounterCashManagementMode] = useState(
439
+ resolvePosCashManagementMode(normalizedInitialConfigs),
440
+ );
441
+ const [checkOrderType, setCheckOrderType] = useState(
442
+ resolvePosCheckOrderTypeForShop(
443
+ normalizedInitialConfigs,
444
+ runtimeCompanyConfigs,
445
+ ),
446
+ );
447
+ const [checkOrderManagementMode, setCheckOrderManagementMode] = useState(
448
+ resolvePosCheckOrderManagementMode(normalizedInitialConfigs),
449
+ );
450
+ const [deviceOrderVisibility, setDeviceOrderVisibility] = useState(
451
+ resolveDeviceOrderVisibility(normalizedInitialConfigs),
452
+ );
453
+ const [deviceDeliveryEnabled, setDeviceDeliveryEnabled] = useState(
454
+ isPosDeliveryEnabled(normalizedInitialConfigs),
455
+ );
456
+ const [deviceAlertSoundEnabled, setDeviceAlertSoundEnabled] = useState(
457
+ isTruthyValue(normalizedInitialConfigs?.[DEVICE_ALERT_SOUND_ENABLED_KEY]),
458
+ );
459
+ const [deviceAlertSoundUrl, setDeviceAlertSoundUrl] = useState(
460
+ String(normalizedInitialConfigs?.[DEVICE_ALERT_SOUND_URL_KEY] || ''),
461
+ );
462
+ const [deviceRuntimeDebugInfoEnabled, setDeviceRuntimeDebugInfoEnabled] =
463
+ useState(
464
+ isTruthyValue(
465
+ normalizedInitialConfigs?.[DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY],
466
+ ),
467
+ );
468
+ const [linkedDisplayId, setLinkedDisplayId] = useState(
469
+ normalizeEntityId(normalizedInitialConfigs?.[DISPLAY_DEVICE_LINK_CONFIG_KEY]),
470
+ );
471
+ const [displayPrinterId, setDisplayPrinterId] = useState(
472
+ normalizeDeviceId(normalizedInitialConfigs?.[DISPLAY_DEVICE_PRINTER_CONFIG_KEY]),
473
+ );
474
+ const [displayAllowPrinterChange, setDisplayAllowPrinterChange] = useState(
475
+ canDisplayChangePrinter(normalizedInitialConfigs),
476
+ );
477
+ const [displayAutoPrintProductEnabled, setDisplayAutoPrintProductEnabled] =
478
+ useState(
479
+ isTruthyValue(
480
+ normalizedInitialConfigs?.[DISPLAY_AUTO_PRINT_PRODUCT_CONFIG_KEY],
481
+ ),
482
+ );
483
+ const [savingDisplayPrintingConfig, setSavingDisplayPrintingConfig] = useState(false);
484
+ const [hasLoadedCurrentConfig, setHasLoadedCurrentConfig] = useState(false);
485
+ const [hasLoadedCompanyConfigs, setHasLoadedCompanyConfigs] = useState(false);
486
+ const [hasLoadedMovementData, setHasLoadedMovementData] = useState(false);
487
+ const hasLoadedCurrentConfigRef = useRef(false);
488
+ const hasLoadedCompanyConfigsRef = useRef(false);
489
+ const hasLoadedMovementDataRef = useRef(false);
490
+ const hasInitializedPdvTabRef = useRef(false);
491
+
492
+ // Edição inline do alias
493
+ const [alias, setAlias] = useState(initialAlias || '');
494
+ const [editingAlias, setEditingAlias] = useState(false);
495
+ const [aliasInput, setAliasInput] = useState(alias);
496
+ const [savingAlias, setSavingAlias] = useState(false);
497
+ const aliasInputRef = useRef(null);
498
+
499
+ useEffect(() => {
500
+ if (editingAlias) {
501
+ return;
502
+ }
503
+
504
+ setAlias(initialAlias || '');
505
+ setAliasInput(initialAlias || '');
506
+ }, [editingAlias, initialAlias]);
507
+
508
+ useEffect(() => {
509
+ hasLoadedCurrentConfigRef.current = hasLoadedCurrentConfig;
510
+ }, [hasLoadedCurrentConfig]);
511
+
512
+ useEffect(() => {
513
+ hasLoadedCompanyConfigsRef.current = hasLoadedCompanyConfigs;
514
+ }, [hasLoadedCompanyConfigs]);
515
+
516
+ useEffect(() => {
517
+ hasLoadedMovementDataRef.current = hasLoadedMovementData;
518
+ }, [hasLoadedMovementData]);
519
+
520
+ const isOpen = useMemo(() => getIsOpen(configs), [configs]);
521
+ const hasLocalPaymentGateway = useMemo(
522
+ () =>
523
+ isPaymentCapableDeviceConfig({
524
+ configs,
525
+ type: deviceType,
526
+ device: {type: deviceType},
527
+ }),
528
+ [configs, deviceType],
529
+ );
530
+ const paymentDeviceOptions = useMemo(
531
+ () =>
532
+ getCompanyPaymentDeviceOptions(
533
+ filterDeviceConfigsByCompany(companyDeviceConfigs, currentCompany?.id),
534
+ ).filter(
535
+ option => option.deviceId !== deviceString,
536
+ ),
537
+ [companyDeviceConfigs, currentCompany?.id, deviceString],
538
+ );
539
+ const displayOptions = useMemo(
540
+ () =>
541
+ (Array.isArray(displays) ? displays : [])
542
+ .filter(option => {
543
+ const companyId = normalizeEntityId(option?.company?.id || option?.company);
544
+ const currentCompanyId = normalizeEntityId(currentCompany?.id);
545
+ return !currentCompanyId || !companyId || companyId === currentCompanyId;
546
+ })
547
+ .sort((left, right) =>
548
+ String(left?.display || '').localeCompare(String(right?.display || '')),
549
+ ),
550
+ [currentCompany?.id, displays],
551
+ );
552
+ const printerOptions = useMemo(
553
+ () =>
554
+ getPrinterOptions({
555
+ printers,
556
+ deviceConfigs: companyDeviceConfigs,
557
+ companyId: currentCompany?.id,
558
+ }),
559
+ [companyDeviceConfigs, currentCompany?.id, printers],
560
+ );
561
+ const selectedPosOperationModeOption = useMemo(
562
+ () => getPosOperationModeOption(posOperationMode),
563
+ [posOperationMode],
564
+ );
565
+ const pickerMode = Platform.OS === 'android' ? 'dropdown' : undefined;
566
+ const packageVersion = packageJson?.version || packageJson?.default?.version;
567
+ const appVersion = packageVersion || runtimeDevice?.appVersion || '';
568
+ const runtimeDeviceId = useMemo(
569
+ () => normalizeDeviceId(runtimeDevice?.id || runtimeDevice?.device),
570
+ [runtimeDevice?.device, runtimeDevice?.id],
571
+ );
572
+ const runtimeDeviceType = useMemo(
573
+ () =>
574
+ String(runtimeDevice?.type || runtimeDevice?.deviceType || '')
575
+ .trim()
576
+ .toUpperCase(),
577
+ [runtimeDevice?.deviceType, runtimeDevice?.type],
578
+ );
579
+ const isEditingRuntimeDevice = useMemo(
580
+ () =>
581
+ !!runtimeDeviceId &&
582
+ runtimeDeviceId === normalizeDeviceId(deviceString) &&
583
+ runtimeDeviceType === deviceType,
584
+ [deviceString, deviceType, runtimeDeviceId, runtimeDeviceType],
585
+ );
586
+
587
+ const resolveDeviceContext = useCallback(async () => {
588
+ if (deviceString && deviceType) {
589
+ return {
590
+ deviceData: null,
591
+ deviceString,
592
+ deviceType,
593
+ };
594
+ }
595
+
596
+ if (!deviceId) {
597
+ return {
598
+ deviceData: null,
599
+ deviceString: '',
600
+ deviceType: '',
601
+ };
602
+ }
603
+
604
+ const fetchedDevice = await actionsRef.current.deviceActions
605
+ .get(deviceId)
606
+ .catch(() => null);
607
+
608
+ return {
609
+ deviceData: fetchedDevice,
610
+ deviceString: String(fetchedDevice?.device || '').trim(),
611
+ deviceType: String(fetchedDevice?.type || fetchedDevice?.deviceType || '')
612
+ .trim()
613
+ .toUpperCase(),
614
+ };
615
+ }, [deviceId, deviceString, deviceType]);
616
+
617
+ const applyCurrentDeviceConfig = useCallback((scopedItems, context = {}) => {
618
+ const currentDeviceString = String(
619
+ context.deviceString || deviceString || '',
620
+ ).trim();
621
+ const currentDeviceType = String(
622
+ context.deviceType || deviceType || '',
623
+ )
624
+ .trim()
625
+ .toUpperCase();
626
+ const currentDeviceConfigId = normalizeEntityId(
627
+ context.deviceId || deviceId,
628
+ );
629
+ const dc = (scopedItems || []).find(d => {
630
+ const currentConfigType = String(d?.type || d?.device?.type || '')
631
+ .trim()
632
+ .toUpperCase();
633
+ const nextDeviceId = normalizeEntityId(
634
+ d?.device?.id ||
635
+ d?.device?.['@id'] ||
636
+ d?.deviceId ||
637
+ d?.device?.deviceId,
638
+ );
639
+
640
+ if (currentDeviceConfigId && nextDeviceId === currentDeviceConfigId) {
641
+ return true;
642
+ }
643
+
644
+ return (
645
+ d?.device?.device === currentDeviceString &&
646
+ currentConfigType === currentDeviceType
647
+ );
648
+ });
649
+
650
+ if (dc) {
651
+ const nextConfigs = parseConfigsObject(dc.configs);
652
+ actionsRef.current.deviceConfigActions.setItem({
653
+ ...dc,
654
+ configs: nextConfigs,
655
+ });
656
+ setConfigs(nextConfigs);
657
+ setDevicePaymentTarget(
658
+ normalizeDeviceId(nextConfigs[ORDER_PAYMENT_DEVICE_CONFIG_KEY]),
659
+ );
660
+ setPdvGateway(getPaymentGatewayFromConfigs(nextConfigs));
661
+ setPdvPrinterEnabled(isPdvPrinterEnabled(nextConfigs));
662
+ setPosOperationMode(resolvePosOperationMode(nextConfigs));
663
+ setProductShowcaseId(
664
+ normalizeEntityId(nextConfigs[POS_PRODUCT_SHOWCASE_CONFIG_KEY]),
665
+ );
666
+ setAndroidKioskEnabled(isAndroidKioskEnabled(nextConfigs));
667
+ setAndroidLauncherEnabled(isAndroidLauncherEnabled(nextConfigs));
668
+ setCounterAutoPrintEnabled(isPosAutoPrintEnabled(nextConfigs));
669
+ setCounterPrintMode(resolvePosPrintMode(nextConfigs));
670
+ setCounterCashManagementMode(resolvePosCashManagementMode(nextConfigs));
671
+ setCheckOrderType(
672
+ resolvePosCheckOrderTypeForShop(nextConfigs, runtimeCompanyConfigs),
673
+ );
674
+ setCheckOrderManagementMode(
675
+ resolvePosCheckOrderManagementMode(nextConfigs),
676
+ );
677
+ setDeviceOrderVisibility(
678
+ resolveDeviceOrderVisibility(nextConfigs),
679
+ );
680
+ setDeviceDeliveryEnabled(
681
+ isPosDeliveryEnabled(nextConfigs),
682
+ );
683
+ setDeviceAlertSoundEnabled(
684
+ isTruthyValue(nextConfigs[DEVICE_ALERT_SOUND_ENABLED_KEY]),
685
+ );
686
+ setDeviceAlertSoundUrl(
687
+ String(nextConfigs[DEVICE_ALERT_SOUND_URL_KEY] || ''),
688
+ );
689
+ setDeviceRuntimeDebugInfoEnabled(
690
+ isTruthyValue(nextConfigs[DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY]),
691
+ );
692
+ setLinkedDisplayId(
693
+ normalizeEntityId(nextConfigs[DISPLAY_DEVICE_LINK_CONFIG_KEY]),
694
+ );
695
+ setDisplayPrinterId(
696
+ normalizeDeviceId(nextConfigs[DISPLAY_DEVICE_PRINTER_CONFIG_KEY]),
697
+ );
698
+ setDisplayAllowPrinterChange(
699
+ canDisplayChangePrinter(nextConfigs),
700
+ );
701
+ setDisplayAutoPrintProductEnabled(
702
+ isTruthyValue(nextConfigs[DISPLAY_AUTO_PRINT_PRODUCT_CONFIG_KEY]),
703
+ );
704
+ return;
705
+ }
706
+
707
+ actionsRef.current.deviceConfigActions.setItem({});
708
+
709
+ setConfigs({});
710
+ setDevicePaymentTarget('');
711
+ setPdvGateway('');
712
+ setPdvPrinterEnabled(true);
713
+ setPosOperationMode(resolvePosOperationMode({}));
714
+ setProductShowcaseId('');
715
+ setAndroidKioskEnabled(false);
716
+ setAndroidLauncherEnabled(false);
717
+ setCounterAutoPrintEnabled(isPosAutoPrintEnabled({}));
718
+ setCounterPrintMode(resolvePosPrintMode({}));
719
+ setCounterCashManagementMode(resolvePosCashManagementMode({}));
720
+ setCheckOrderType(
721
+ resolvePosCheckOrderTypeForShop({}, runtimeCompanyConfigs),
722
+ );
723
+ setCheckOrderManagementMode(resolvePosCheckOrderManagementMode({}));
724
+ setDeviceOrderVisibility(DEVICE_ORDER_VISIBILITY_DEVICE);
725
+ setDeviceDeliveryEnabled(isPosDeliveryEnabled({}));
726
+ setDeviceAlertSoundEnabled(false);
727
+ setDeviceAlertSoundUrl('');
728
+ setDeviceRuntimeDebugInfoEnabled(false);
729
+ setLinkedDisplayId('');
730
+ setDisplayPrinterId('');
731
+ setDisplayAllowPrinterChange(false);
732
+ setDisplayAutoPrintProductEnabled(false);
733
+ }, [deviceId, deviceString, deviceType]);
734
+
735
+ const loadMovementData = useCallback(async () => {
736
+ if (!isPdvDevice) {
737
+ setProducts([]);
738
+ setInflowData(null);
739
+ setHasLoadedMovementData(false);
740
+ return;
741
+ }
742
+
743
+ if (!currentCompany?.id) return;
744
+ const resolvedContext = await resolveDeviceContext();
745
+ const nextDeviceString = resolvedContext?.deviceString || deviceString;
746
+ if (!nextDeviceString) return;
747
+ setLoadingMovementData(true);
748
+ try {
749
+ const [cashData, inflowRaw] = await Promise.all([
750
+ actionsRef.current.invoiceActions.getCashRegister({
751
+ device: nextDeviceString,
752
+ provider: currentCompany.id,
753
+ }),
754
+ actionsRef.current.invoiceActions.getInflow({
755
+ 'device.device': nextDeviceString,
756
+ receiver: currentCompany.id,
757
+ }),
758
+ ]);
759
+
760
+ setProducts(Array.isArray(cashData) ? cashData : []);
761
+
762
+ // getInflow retorna data['member'] = [{ payments: {...} }]
763
+ const member = Array.isArray(inflowRaw) ? inflowRaw : [];
764
+ setInflowData(member[0]?.payments || null);
765
+ } catch {
766
+ setProducts([]);
767
+ setInflowData(null);
768
+ } finally {
769
+ setLoadingMovementData(false);
770
+ setHasLoadedMovementData(true);
771
+ }
772
+ }, [currentCompany?.id, deviceString, isPdvDevice, resolveDeviceContext]);
773
+
774
+ const refreshCurrentConfig = useCallback(async () => {
775
+ if (!currentCompany?.id) return;
776
+ setLoadingConfigData(true);
777
+ try {
778
+ const resolvedContext = await resolveDeviceContext();
779
+ const nextDeviceString = resolvedContext?.deviceString || deviceString;
780
+ const nextDeviceType = resolvedContext?.deviceType || deviceType;
781
+ if (!nextDeviceString || !nextDeviceType) {
782
+ applyCurrentDeviceConfig([], {
783
+ deviceId,
784
+ deviceString: nextDeviceString,
785
+ deviceType: nextDeviceType,
786
+ });
787
+ return;
788
+ }
789
+
790
+ const items = await actionsRef.current.deviceConfigActions.getItems({
791
+ 'device.device': nextDeviceString,
792
+ people: `/people/${currentCompany.id}`,
793
+ type: nextDeviceType,
794
+ });
795
+ const scopedItems = filterDeviceConfigsByCompany(items, currentCompany?.id);
796
+ const selectedDeviceConfig = scopedItems.find(d => {
797
+ const currentConfigType = String(d?.type || d?.device?.type || '')
798
+ .trim()
799
+ .toUpperCase();
800
+ const nextDeviceId = normalizeEntityId(
801
+ d?.device?.id ||
802
+ d?.device?.['@id'] ||
803
+ d?.deviceId ||
804
+ d?.device?.deviceId,
805
+ );
806
+
807
+ return (
808
+ (deviceId && nextDeviceId === deviceId) ||
809
+ (d?.device?.device === nextDeviceString &&
810
+ currentConfigType === nextDeviceType)
811
+ );
812
+ });
813
+
814
+ if (selectedDeviceConfig) {
815
+ actionsRef.current.deviceConfigActions.setItem({
816
+ ...selectedDeviceConfig,
817
+ configs: parseConfigsObject(selectedDeviceConfig.configs),
818
+ });
819
+ }
820
+
821
+ applyCurrentDeviceConfig(
822
+ selectedDeviceConfig ? [selectedDeviceConfig] : scopedItems,
823
+ {
824
+ deviceId,
825
+ deviceString: nextDeviceString,
826
+ deviceType: nextDeviceType,
827
+ },
828
+ );
829
+ } catch {
830
+ applyCurrentDeviceConfig([], {deviceId});
831
+ } finally {
832
+ setLoadingConfigData(false);
833
+ setHasLoadedCurrentConfig(true);
834
+ }
835
+ }, [
836
+ applyCurrentDeviceConfig,
837
+ currentCompany?.id,
838
+ deviceString,
839
+ deviceType,
840
+ deviceId,
841
+ resolveDeviceContext,
842
+ ]);
843
+
844
+ const loadCompanyConfigs = useCallback(async () => {
845
+ if (!currentCompany?.id) return;
846
+
847
+ setLoadingCompanyDeviceConfigs(true);
848
+ try {
849
+ const items = await actionsRef.current.deviceConfigActions.getItems({
850
+ people: `/people/${currentCompany.id}`,
851
+ });
852
+ const scopedItems = filterDeviceConfigsByCompany(items, currentCompany?.id);
853
+ setCompanyDeviceConfigs(Array.isArray(scopedItems) ? scopedItems : []);
854
+ } catch {
855
+ setCompanyDeviceConfigs([]);
856
+ } finally {
857
+ setLoadingCompanyDeviceConfigs(false);
858
+ setHasLoadedCompanyConfigs(true);
859
+ }
860
+ }, [currentCompany?.id]);
861
+
862
+ const loadProductShowcases = useCallback(async () => {
863
+ if (!currentCompany?.id || !isPdvDevice) {
864
+ setProductShowcases([]);
865
+ return;
866
+ }
867
+
868
+ setLoadingProductShowcases(true);
869
+ try {
870
+ const response = await api.fetch('/product_showcases', {
871
+ params: {
872
+ company: `/people/${currentCompany.id}`,
873
+ integrationKey: 'pos',
874
+ active: 1,
875
+ 'order[name]': 'ASC',
876
+ },
877
+ });
878
+ const items =
879
+ response?.member ||
880
+ response?.['hydra:member'] ||
881
+ response?.items ||
882
+ response;
883
+ setProductShowcases(Array.isArray(items) ? items : []);
884
+ } catch {
885
+ setProductShowcases([]);
886
+ } finally {
887
+ setLoadingProductShowcases(false);
888
+ }
889
+ }, [currentCompany?.id, isPdvDevice]);
890
+
891
+ const ensureActiveTabData = useCallback(async ({ force = false } = {}) => {
892
+ if (!currentCompany?.id) {
893
+ return;
894
+ }
895
+
896
+ if (!isPdvDevice) {
897
+ await Promise.all([
898
+ refreshCurrentConfig(),
899
+ loadCompanyConfigs(),
900
+ ]);
901
+ return;
902
+ }
903
+
904
+ if (activePdvTab === PDV_TAB_MOVEMENT) {
905
+ if (!force && hasLoadedMovementDataRef.current) {
906
+ return;
907
+ }
908
+ await loadMovementData();
909
+ return;
910
+ }
911
+
912
+ if (activePdvTab === PDV_TAB_ORDERS) {
913
+ const pendingLoads = [];
914
+ if (force || !hasLoadedCurrentConfigRef.current) {
915
+ pendingLoads.push(refreshCurrentConfig());
916
+ }
917
+ if (force || !hasLoadedCompanyConfigsRef.current) {
918
+ pendingLoads.push(loadCompanyConfigs());
919
+ }
920
+
921
+ if (pendingLoads.length > 0) {
922
+ await Promise.all(pendingLoads);
923
+ }
924
+ return;
925
+ }
926
+
927
+ if (force || !hasLoadedCurrentConfig) {
928
+ await refreshCurrentConfig();
929
+ }
930
+
931
+ if (activePdvTab === PDV_TAB_OPERATION) {
932
+ await loadProductShowcases();
933
+ }
934
+ }, [
935
+ activePdvTab,
936
+ currentCompany?.id,
937
+ isPdvDevice,
938
+ loadCompanyConfigs,
939
+ loadMovementData,
940
+ loadProductShowcases,
941
+ refreshCurrentConfig,
942
+ ]);
943
+
944
+ useEffect(() => {
945
+ setHasLoadedCurrentConfig(false);
946
+ setHasLoadedCompanyConfigs(false);
947
+ setHasLoadedMovementData(false);
948
+ hasInitializedPdvTabRef.current = false;
949
+ setProducts([]);
950
+ setInflowData(null);
951
+ setCompanyDeviceConfigs([]);
952
+ }, [currentCompany?.id, deviceString, deviceType]);
953
+
954
+ const ensureActiveTabDataRef = useRef(ensureActiveTabData);
955
+ useEffect(() => {
956
+ ensureActiveTabDataRef.current = ensureActiveTabData;
957
+ }, [ensureActiveTabData]);
958
+
959
+ useFocusEffect(
960
+ useCallback(() => {
961
+ ensureActiveTabDataRef.current({ force: true });
962
+ }, []),
963
+ );
964
+
965
+ useEffect(() => {
966
+ if (!isPdvDevice) {
967
+ return;
968
+ }
969
+
970
+ if (!hasInitializedPdvTabRef.current) {
971
+ hasInitializedPdvTabRef.current = true;
972
+ return;
973
+ }
974
+
975
+ ensureActiveTabDataRef.current();
976
+ }, [activePdvTab, isPdvDevice]);
977
+
978
+ useFocusEffect(
979
+ useCallback(() => {
980
+ if (!isDisplayDevice || !currentCompany?.id) {
981
+ return;
982
+ }
983
+
984
+ displayStore.actions
985
+ .getItems({
986
+ company: currentCompany.id,
987
+ })
988
+ .catch(() => {});
989
+ printerStore.actions
990
+ .getPrinters({people: currentCompany.id})
991
+ .catch(() => {});
992
+ }, [
993
+ currentCompany?.id,
994
+ displayStore.actions,
995
+ isDisplayDevice,
996
+ printerStore.actions,
997
+ ]),
998
+ );
999
+
1000
+ const handleToggle = useCallback(() => {
1001
+ if (!isPdvDevice) {
1002
+ return;
1003
+ }
1004
+
1005
+ const msg = isOpen ? 'Deseja fechar o caixa?' : 'Deseja abrir o caixa?';
1006
+ confirm(msg, async () => {
1007
+ setActionLoading(true);
1008
+ try {
1009
+ const action = isOpen
1010
+ ? actionsRef.current.invoiceActions.closeCashRegister
1011
+ : actionsRef.current.invoiceActions.openCashRegister;
1012
+ await action({ device: deviceString, provider: currentCompany.id });
1013
+ await refreshCurrentConfig();
1014
+ if (activePdvTab === PDV_TAB_MOVEMENT || hasLoadedMovementData) {
1015
+ await loadMovementData();
1016
+ }
1017
+ } catch (error) {
1018
+ showSystemError(
1019
+ error,
1020
+ 'Nao foi possivel atualizar o caixa deste device.',
1021
+ );
1022
+ } finally {
1023
+ setActionLoading(false);
1024
+ }
1025
+ });
1026
+ }, [
1027
+ activePdvTab,
1028
+ currentCompany?.id,
1029
+ deviceString,
1030
+ hasLoadedMovementData,
1031
+ isOpen,
1032
+ isPdvDevice,
1033
+ loadMovementData,
1034
+ refreshCurrentConfig,
1035
+ showSystemError,
1036
+ ]);
1037
+
1038
+ const startEditAlias = useCallback(() => {
1039
+ setAliasInput(alias);
1040
+ setEditingAlias(true);
1041
+ setTimeout(() => aliasInputRef.current?.focus(), 80);
1042
+ }, [alias]);
1043
+
1044
+ const cancelEditAlias = useCallback(() => {
1045
+ setEditingAlias(false);
1046
+ setAliasInput(alias);
1047
+ }, [alias]);
1048
+
1049
+ const saveAlias = useCallback(async () => {
1050
+ const trimmed = aliasInput.trim();
1051
+ if (!trimmed || trimmed === alias || !deviceId) {
1052
+ cancelEditAlias();
1053
+ return;
1054
+ }
1055
+ setSavingAlias(true);
1056
+ try {
1057
+ const savedDevice = await actionsRef.current.deviceActions.save({
1058
+ id: deviceId,
1059
+ alias: trimmed,
1060
+ });
1061
+ const nextAlias = String(savedDevice?.alias || trimmed).trim();
1062
+ setAlias(nextAlias);
1063
+ setAliasInput(nextAlias);
1064
+ setEditingAlias(false);
1065
+ } catch (error) {
1066
+ showSystemError(error, 'Nao foi possivel salvar o nome do device.');
1067
+ cancelEditAlias();
1068
+ } finally {
1069
+ setSavingAlias(false);
1070
+ }
1071
+ }, [aliasInput, alias, deviceId, cancelEditAlias, showSystemError]);
1072
+
1073
+ const saveDevicePaymentTarget = useCallback(async (override = {}) => {
1074
+ const nextDevicePaymentTarget =
1075
+ override.devicePaymentTarget ?? devicePaymentTarget;
1076
+
1077
+ if (!currentCompany?.id || !deviceString) {
1078
+ return;
1079
+ }
1080
+
1081
+ setSavingPaymentTarget(true);
1082
+ try {
1083
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1084
+ device: deviceString,
1085
+ configs: JSON.stringify({
1086
+ [ORDER_PAYMENT_DEVICE_CONFIG_KEY]: nextDevicePaymentTarget || '',
1087
+ }),
1088
+ people: '/people/' + currentCompany.id,
1089
+ type: deviceType,
1090
+ });
1091
+ await refreshCurrentConfig();
1092
+ } catch (error) {
1093
+ showSystemError(
1094
+ error,
1095
+ 'Nao foi possivel salvar o device de pagamento.',
1096
+ );
1097
+ } finally {
1098
+ setSavingPaymentTarget(false);
1099
+ }
1100
+ }, [currentCompany?.id, devicePaymentTarget, deviceString, deviceType, refreshCurrentConfig, showSystemError]);
1101
+
1102
+ const savePdvSettings = useCallback(async (override = {}) => {
1103
+ const nextPdvGateway = override.pdvGateway ?? pdvGateway;
1104
+ const nextPdvPrinterEnabled =
1105
+ override.pdvPrinterEnabled ?? pdvPrinterEnabled;
1106
+
1107
+ if (
1108
+ !isPdvDevice ||
1109
+ !currentCompany?.id ||
1110
+ !deviceString ||
1111
+ savingPdvSettings
1112
+ ) {
1113
+ return;
1114
+ }
1115
+
1116
+ setSavingPdvSettings(true);
1117
+ try {
1118
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1119
+ device: deviceString,
1120
+ configs: JSON.stringify({
1121
+ [POS_GATEWAY_CONFIG_KEY]: nextPdvGateway || '',
1122
+ [PDV_PRINTER_ENABLED_CONFIG_KEY]: nextPdvPrinterEnabled ? '1' : '0',
1123
+ }),
1124
+ people: '/people/' + currentCompany.id,
1125
+ type: deviceType,
1126
+ });
1127
+ await refreshCurrentConfig();
1128
+ } catch (error) {
1129
+ showSystemError(
1130
+ error,
1131
+ 'Nao foi possivel salvar as configuracoes de pagamento do PDV.',
1132
+ );
1133
+ } finally {
1134
+ setSavingPdvSettings(false);
1135
+ }
1136
+ }, [
1137
+ currentCompany?.id,
1138
+ deviceString,
1139
+ deviceType,
1140
+ isPdvDevice,
1141
+ pdvGateway,
1142
+ pdvPrinterEnabled,
1143
+ refreshCurrentConfig,
1144
+ savingPdvSettings,
1145
+ showSystemError,
1146
+ ]);
1147
+
1148
+ const saveProductShowcaseConfig = useCallback(async (override = {}) => {
1149
+ const nextProductShowcaseId =
1150
+ override.productShowcaseId ?? productShowcaseId;
1151
+
1152
+ if (!currentCompany?.id || !deviceString || savingProductShowcase) {
1153
+ return;
1154
+ }
1155
+
1156
+ setSavingProductShowcase(true);
1157
+ try {
1158
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1159
+ device: deviceString,
1160
+ configs: JSON.stringify({
1161
+ [POS_PRODUCT_SHOWCASE_CONFIG_KEY]: nextProductShowcaseId || '',
1162
+ }),
1163
+ people: '/people/' + currentCompany.id,
1164
+ type: deviceType,
1165
+ });
1166
+ await refreshCurrentConfig();
1167
+ } catch (error) {
1168
+ showSystemError(
1169
+ error,
1170
+ 'Nao foi possivel salvar a vitrine deste PDV.',
1171
+ );
1172
+ } finally {
1173
+ setSavingProductShowcase(false);
1174
+ }
1175
+ }, [
1176
+ currentCompany?.id,
1177
+ deviceString,
1178
+ deviceType,
1179
+ productShowcaseId,
1180
+ refreshCurrentConfig,
1181
+ savingProductShowcase,
1182
+ showSystemError,
1183
+ ]);
1184
+
1185
+ const savePaymentTypeConfigs = useCallback(
1186
+ async nextSelectedPaymentTypeIds => {
1187
+ if (
1188
+ !currentCompany?.id ||
1189
+ !deviceString ||
1190
+ savingPaymentTypes
1191
+ ) {
1192
+ return;
1193
+ }
1194
+
1195
+ setSavingPaymentTypes(true);
1196
+ try {
1197
+ const nextConfigs = appendScreenMetrics({
1198
+ ...(configs || {}),
1199
+ [PAYMENT_TYPE_IDS_CONFIG_KEY]: nextSelectedPaymentTypeIds,
1200
+ 'config-version': appVersion,
1201
+ });
1202
+
1203
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1204
+ device: deviceString,
1205
+ configs: JSON.stringify(nextConfigs),
1206
+ people: '/people/' + currentCompany.id,
1207
+ type: deviceType,
1208
+ });
1209
+
1210
+ actionsRef.current.deviceConfigActions.setItem({
1211
+ ...(currentDeviceConfig || {}),
1212
+ configs: nextConfigs,
1213
+ device:
1214
+ currentDeviceConfig?.device ||
1215
+ currentDevice ||
1216
+ {device: deviceString, type: deviceType},
1217
+ people:
1218
+ currentDeviceConfig?.people ||
1219
+ `/people/${currentCompany.id}`,
1220
+ type:
1221
+ currentDeviceConfig?.type ||
1222
+ deviceType,
1223
+ });
1224
+ setConfigs(nextConfigs);
1225
+ } catch (error) {
1226
+ showSystemError(
1227
+ error,
1228
+ 'Nao foi possivel salvar os tipos de pagamento do device.',
1229
+ );
1230
+ } finally {
1231
+ setSavingPaymentTypes(false);
1232
+ }
1233
+ },
1234
+ [
1235
+ currentCompany?.id,
1236
+ appVersion,
1237
+ configs,
1238
+ currentDevice,
1239
+ currentDeviceConfig,
1240
+ deviceString,
1241
+ deviceType,
1242
+ savingPaymentTypes,
1243
+ showSystemError,
1244
+ ],
1245
+ );
1246
+
1247
+ const savePosOperationMode = useCallback(async (override = {}) => {
1248
+ const nextPosOperationMode =
1249
+ override.posOperationMode ?? posOperationMode;
1250
+ const nextAndroidKioskEnabled =
1251
+ override.androidKioskEnabled ?? androidKioskEnabled;
1252
+ const nextCheckOrderType =
1253
+ override.checkOrderType ?? checkOrderType;
1254
+ const nextCheckOrderManagementMode =
1255
+ override.checkOrderManagementMode ?? checkOrderManagementMode;
1256
+ const nextCounterAutoPrintEnabled =
1257
+ override.counterAutoPrintEnabled ?? counterAutoPrintEnabled;
1258
+ const nextCounterPrintMode =
1259
+ override.counterPrintMode ?? counterPrintMode;
1260
+ const nextCounterCashManagementMode =
1261
+ override.counterCashManagementMode ?? counterCashManagementMode;
1262
+
1263
+ if (
1264
+ !isPdvDevice ||
1265
+ !currentCompany?.id ||
1266
+ !deviceString ||
1267
+ savingPosOperationMode
1268
+ ) {
1269
+ return;
1270
+ }
1271
+
1272
+ setSavingPosOperationMode(true);
1273
+ try {
1274
+ const nextOperationConfigs = {
1275
+ [POS_OPERATION_MODE_CONFIG_KEY]: nextPosOperationMode,
1276
+ [DEVICE_ANDROID_KIOSK_ENABLED_CONFIG_KEY]: nextAndroidKioskEnabled
1277
+ ? '1'
1278
+ : '0',
1279
+ [POS_CHECK_ORDER_TYPE_CONFIG_KEY]: nextCheckOrderType,
1280
+ [POS_CHECK_ORDER_MANAGEMENT_MODE_CONFIG_KEY]:
1281
+ nextCheckOrderType === POS_CHECK_ORDER_TYPE_NONE
1282
+ ? POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE
1283
+ : nextCheckOrderManagementMode,
1284
+ };
1285
+
1286
+ if (nextPosOperationMode === POS_OPERATION_MODE_COUNTER) {
1287
+ nextOperationConfigs[POS_AUTO_PRINT_ENABLED_CONFIG_KEY] =
1288
+ nextCounterAutoPrintEnabled ? '1' : '0';
1289
+ nextOperationConfigs['print-mode'] = nextCounterPrintMode;
1290
+ nextOperationConfigs[POS_CASH_MANAGEMENT_MODE_CONFIG_KEY] =
1291
+ nextCounterCashManagementMode;
1292
+ }
1293
+
1294
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1295
+ device: deviceString,
1296
+ configs: JSON.stringify(nextOperationConfigs),
1297
+ people: '/people/' + currentCompany.id,
1298
+ type: deviceType,
1299
+ });
1300
+ await refreshCurrentConfig();
1301
+ } catch (error) {
1302
+ showSystemError(
1303
+ error,
1304
+ 'Nao foi possivel salvar a operacao do PDV.',
1305
+ );
1306
+ } finally {
1307
+ setSavingPosOperationMode(false);
1308
+ }
1309
+ }, [
1310
+ currentCompany?.id,
1311
+ checkOrderManagementMode,
1312
+ checkOrderType,
1313
+ counterAutoPrintEnabled,
1314
+ counterCashManagementMode,
1315
+ counterPrintMode,
1316
+ deviceString,
1317
+ deviceType,
1318
+ androidKioskEnabled,
1319
+ isPdvDevice,
1320
+ posOperationMode,
1321
+ refreshCurrentConfig,
1322
+ savingPosOperationMode,
1323
+ showSystemError,
1324
+ ]);
1325
+
1326
+ useEffect(() => {
1327
+ if (!isPdvDevice || !currentCompany?.id || !deviceString) {
1328
+ return;
1329
+ }
1330
+
1331
+ const rawCheckOrderType = resolvePosCheckOrderType(configs);
1332
+ const nextCheckOrderType = resolvePosCheckOrderTypeForShop(
1333
+ configs,
1334
+ runtimeCompanyConfigs,
1335
+ );
1336
+
1337
+ if (
1338
+ rawCheckOrderType !== POS_CHECK_ORDER_TYPE_STAMP ||
1339
+ nextCheckOrderType !== POS_CHECK_ORDER_TYPE_NONE ||
1340
+ savingPosOperationMode
1341
+ ) {
1342
+ stampAutoDisableSignatureRef.current = '';
1343
+ return;
1344
+ }
1345
+
1346
+ const signature = [
1347
+ currentCompany?.id,
1348
+ deviceString,
1349
+ rawCheckOrderType,
1350
+ loyaltyCouponsEnabled ? '1' : '0',
1351
+ String(configs?.['config-version'] || ''),
1352
+ ].join(':');
1353
+
1354
+ if (stampAutoDisableSignatureRef.current === signature) {
1355
+ return;
1356
+ }
1357
+
1358
+ stampAutoDisableSignatureRef.current = signature;
1359
+
1360
+ setCheckOrderType(POS_CHECK_ORDER_TYPE_NONE);
1361
+ setCheckOrderManagementMode(POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE);
1362
+ savePosOperationMode({
1363
+ checkOrderType: POS_CHECK_ORDER_TYPE_NONE,
1364
+ checkOrderManagementMode: POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE,
1365
+ });
1366
+ }, [
1367
+ configs,
1368
+ currentCompany?.id,
1369
+ deviceString,
1370
+ isPdvDevice,
1371
+ loyaltyCouponsEnabled,
1372
+ savePosOperationMode,
1373
+ savingPosOperationMode,
1374
+ runtimeCompanyConfigs,
1375
+ ]);
1376
+
1377
+ const saveLauncherMode = useCallback(async (override = {}) => {
1378
+ const nextAndroidLauncherEnabled =
1379
+ override.androidLauncherEnabled ?? androidLauncherEnabled;
1380
+
1381
+ if (
1382
+ !isPdvDevice ||
1383
+ !currentCompany?.id ||
1384
+ !deviceString ||
1385
+ savingLauncherMode
1386
+ ) {
1387
+ return;
1388
+ }
1389
+
1390
+ setSavingLauncherMode(true);
1391
+ try {
1392
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1393
+ device: deviceString,
1394
+ configs: JSON.stringify({
1395
+ [DEVICE_ANDROID_LAUNCHER_ENABLED_CONFIG_KEY]: nextAndroidLauncherEnabled
1396
+ ? '1'
1397
+ : '0',
1398
+ 'config-version': appVersion,
1399
+ }),
1400
+ people: '/people/' + currentCompany.id,
1401
+ type: deviceType,
1402
+ });
1403
+ await refreshCurrentConfig();
1404
+ } catch (error) {
1405
+ showSystemError(
1406
+ error,
1407
+ 'Nao foi possivel salvar o launcher do device.',
1408
+ );
1409
+ } finally {
1410
+ setSavingLauncherMode(false);
1411
+ }
1412
+ }, [
1413
+ androidLauncherEnabled,
1414
+ appVersion,
1415
+ currentCompany?.id,
1416
+ deviceString,
1417
+ deviceType,
1418
+ isPdvDevice,
1419
+ refreshCurrentConfig,
1420
+ savingLauncherMode,
1421
+ showSystemError,
1422
+ ]);
1423
+
1424
+ const saveDeviceAlertSoundConfig = useCallback(async (override = {}) => {
1425
+ const nextDeviceAlertSoundEnabled =
1426
+ override.deviceAlertSoundEnabled ?? deviceAlertSoundEnabled;
1427
+ const nextDeviceAlertSoundUrl =
1428
+ override.deviceAlertSoundUrl ?? deviceAlertSoundUrl;
1429
+
1430
+ if (!currentCompany?.id || !deviceString || savingAlertSound) {
1431
+ return;
1432
+ }
1433
+
1434
+ setSavingAlertSound(true);
1435
+ try {
1436
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1437
+ device: deviceString,
1438
+ configs: JSON.stringify({
1439
+ [DEVICE_ALERT_SOUND_ENABLED_KEY]: nextDeviceAlertSoundEnabled ? '1' : '0',
1440
+ [DEVICE_ALERT_SOUND_URL_KEY]: String(nextDeviceAlertSoundUrl || '').trim(),
1441
+ }),
1442
+ people: '/people/' + currentCompany.id,
1443
+ type: deviceType,
1444
+ });
1445
+ await refreshCurrentConfig();
1446
+ } catch (error) {
1447
+ showSystemError(
1448
+ error,
1449
+ 'Nao foi possivel salvar o som de alerta do device.',
1450
+ );
1451
+ } finally {
1452
+ setSavingAlertSound(false);
1453
+ }
1454
+ }, [
1455
+ currentCompany?.id,
1456
+ deviceAlertSoundEnabled,
1457
+ deviceAlertSoundUrl,
1458
+ deviceString,
1459
+ deviceType,
1460
+ refreshCurrentConfig,
1461
+ savingAlertSound,
1462
+ showSystemError,
1463
+ ]);
1464
+
1465
+ const saveDeviceOrderVisibility = useCallback(async (override = {}) => {
1466
+ const nextDeviceOrderVisibility =
1467
+ override.deviceOrderVisibility ?? deviceOrderVisibility;
1468
+
1469
+ if (!currentCompany?.id || !deviceString || savingOrderVisibility) {
1470
+ return;
1471
+ }
1472
+
1473
+ setSavingOrderVisibility(true);
1474
+ try {
1475
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1476
+ device: deviceString,
1477
+ configs: JSON.stringify({
1478
+ [DEVICE_ORDER_VISIBILITY_KEY]: nextDeviceOrderVisibility || DEVICE_ORDER_VISIBILITY_DEVICE,
1479
+ }),
1480
+ people: '/people/' + currentCompany.id,
1481
+ type: deviceType,
1482
+ });
1483
+ await refreshCurrentConfig();
1484
+ } catch (error) {
1485
+ showSystemError(
1486
+ error,
1487
+ 'Nao foi possivel salvar a visibilidade dos pedidos.',
1488
+ );
1489
+ } finally {
1490
+ setSavingOrderVisibility(false);
1491
+ }
1492
+ }, [
1493
+ currentCompany?.id,
1494
+ deviceOrderVisibility,
1495
+ deviceString,
1496
+ deviceType,
1497
+ refreshCurrentConfig,
1498
+ savingOrderVisibility,
1499
+ showSystemError,
1500
+ ]);
1501
+
1502
+ const saveDeviceDeliverySettings = useCallback(async (override = {}) => {
1503
+ const nextDeviceDeliveryEnabled =
1504
+ override.deviceDeliveryEnabled ?? deviceDeliveryEnabled;
1505
+
1506
+ if (
1507
+ !currentCompany?.id ||
1508
+ !deviceString ||
1509
+ savingDeviceDeliverySettings
1510
+ ) {
1511
+ return;
1512
+ }
1513
+
1514
+ setSavingDeviceDeliverySettings(true);
1515
+ try {
1516
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1517
+ device: deviceString,
1518
+ configs: JSON.stringify({
1519
+ [POS_DELIVERY_ENABLED_CONFIG_KEY]: nextDeviceDeliveryEnabled ? '1' : '0',
1520
+ }),
1521
+ people: '/people/' + currentCompany.id,
1522
+ type: deviceType,
1523
+ });
1524
+ await refreshCurrentConfig();
1525
+ } catch (error) {
1526
+ showSystemError(
1527
+ error,
1528
+ 'Nao foi possivel salvar a configuracao de entregas.',
1529
+ );
1530
+ } finally {
1531
+ setSavingDeviceDeliverySettings(false);
1532
+ }
1533
+ }, [
1534
+ currentCompany?.id,
1535
+ deviceDeliveryEnabled,
1536
+ deviceString,
1537
+ deviceType,
1538
+ refreshCurrentConfig,
1539
+ savingDeviceDeliverySettings,
1540
+ showSystemError,
1541
+ ]);
1542
+
1543
+ const saveDeviceRuntimeDebugInfo = useCallback(async (override = {}) => {
1544
+ const nextDeviceRuntimeDebugInfoEnabled =
1545
+ override.deviceRuntimeDebugInfoEnabled ?? deviceRuntimeDebugInfoEnabled;
1546
+
1547
+ if (!currentCompany?.id || !deviceString || savingRuntimeDebugInfo) {
1548
+ return;
1549
+ }
1550
+
1551
+ setSavingRuntimeDebugInfo(true);
1552
+ try {
1553
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1554
+ device: deviceString,
1555
+ configs: JSON.stringify({
1556
+ [DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY]:
1557
+ nextDeviceRuntimeDebugInfoEnabled ? '1' : '0',
1558
+ }),
1559
+ people: '/people/' + currentCompany.id,
1560
+ type: deviceType,
1561
+ });
1562
+ await refreshCurrentConfig();
1563
+ } catch (error) {
1564
+ showSystemError(
1565
+ error,
1566
+ 'Nao foi possivel salvar a exibicao das informacoes tecnicas.',
1567
+ );
1568
+ } finally {
1569
+ setSavingRuntimeDebugInfo(false);
1570
+ }
1571
+ }, [
1572
+ currentCompany?.id,
1573
+ deviceRuntimeDebugInfoEnabled,
1574
+ deviceString,
1575
+ deviceType,
1576
+ refreshCurrentConfig,
1577
+ savingRuntimeDebugInfo,
1578
+ showSystemError,
1579
+ ]);
1580
+
1581
+ const saveDisplayPrintingConfig = useCallback(async (override = {}) => {
1582
+ const nextLinkedDisplayId = override.linkedDisplayId ?? linkedDisplayId;
1583
+ const nextDisplayPrinterId = override.displayPrinterId ?? displayPrinterId;
1584
+ const nextDisplayAllowPrinterChange =
1585
+ override.displayAllowPrinterChange ?? displayAllowPrinterChange;
1586
+ const nextDisplayAutoPrintProductEnabled =
1587
+ override.displayAutoPrintProductEnabled ?? displayAutoPrintProductEnabled;
1588
+
1589
+ if (
1590
+ !isDisplayDevice ||
1591
+ !currentCompany?.id ||
1592
+ !deviceString ||
1593
+ savingDisplayPrintingConfig
1594
+ ) {
1595
+ return;
1596
+ }
1597
+
1598
+ const normalizedDisplayId = String(nextLinkedDisplayId || '').trim();
1599
+ const normalizedPrinterId = normalizeDeviceId(nextDisplayPrinterId);
1600
+
1601
+ if (
1602
+ nextDisplayAutoPrintProductEnabled &&
1603
+ (!normalizedDisplayId || !normalizedPrinterId)
1604
+ ) {
1605
+ return;
1606
+ }
1607
+
1608
+ setSavingDisplayPrintingConfig(true);
1609
+ try {
1610
+ await actionsRef.current.deviceConfigActions.addDeviceConfigs({
1611
+ device: deviceString,
1612
+ configs: JSON.stringify({
1613
+ [DISPLAY_DEVICE_LINK_CONFIG_KEY]: normalizedDisplayId,
1614
+ [DISPLAY_DEVICE_PRINTER_CONFIG_KEY]: normalizedPrinterId,
1615
+ [DISPLAY_ALLOW_PRINTER_CHANGE_CONFIG_KEY]:
1616
+ nextDisplayAllowPrinterChange ? '1' : '0',
1617
+ [DISPLAY_AUTO_PRINT_PRODUCT_CONFIG_KEY]:
1618
+ nextDisplayAutoPrintProductEnabled ? '1' : '0',
1619
+ }),
1620
+ people: '/people/' + currentCompany.id,
1621
+ type: deviceType,
1622
+ });
1623
+ await refreshCurrentConfig();
1624
+ } catch (error) {
1625
+ showSystemError(
1626
+ error,
1627
+ 'Nao foi possivel salvar a configuracao de impressao.',
1628
+ );
1629
+ } finally {
1630
+ setSavingDisplayPrintingConfig(false);
1631
+ }
1632
+ }, [
1633
+ currentCompany?.id,
1634
+ deviceString,
1635
+ deviceType,
1636
+ displayAllowPrinterChange,
1637
+ displayAutoPrintProductEnabled,
1638
+ displayPrinterId,
1639
+ isDisplayDevice,
1640
+ linkedDisplayId,
1641
+ refreshCurrentConfig,
1642
+ savingDisplayPrintingConfig,
1643
+ showSystemError,
1644
+ ]);
1645
+
1646
+ const sendCatalogRefreshCommand = useCallback(() => {
1647
+ if (!currentCompany?.id || !deviceString || sendingCatalogRefresh) {
1648
+ return;
1649
+ }
1650
+
1651
+ confirm('Deseja limpar o cache de produtos deste device?', async () => {
1652
+ setSendingCatalogRefresh(true);
1653
+ try {
1654
+ await websocketActions.send({
1655
+ destination: deviceString,
1656
+ store: 'categories',
1657
+ command: 'clear-product-cache',
1658
+ companyId: currentCompany.id,
1659
+ });
1660
+ } catch (error) {
1661
+ showSystemError(
1662
+ error,
1663
+ 'Nao foi possivel enviar o comando para limpar o cache de produtos.',
1664
+ );
1665
+ } finally {
1666
+ setSendingCatalogRefresh(false);
1667
+ }
1668
+ });
1669
+ }, [currentCompany?.id, deviceString, sendingCatalogRefresh, showSystemError, websocketActions]);
1670
+
1671
+ // Totais derivados
1672
+ const productTotal = useMemo(
1673
+ () => products.reduce((s, p) => s + Number(p.order_product_total || 0), 0),
1674
+ [products],
1675
+ );
1676
+
1677
+ const inflowTotal = inflowData?.total ?? productTotal;
1678
+
1679
+ const wallets = useMemo(() => {
1680
+ if (!inflowData?.wallet) return [];
1681
+ return Object.values(inflowData.wallet).map(w => ({
1682
+ wallet: w.wallet,
1683
+ total: w.total || 0,
1684
+ payments: Object.values(w.payment || {}).filter(pt => (pt.inflow || 0) > 0),
1685
+ }));
1686
+ }, [inflowData]);
1687
+
1688
+ const filteredProducts = useMemo(() => {
1689
+ const term = search.trim().toLowerCase();
1690
+ if (!term) return products;
1691
+ return products.filter(p =>
1692
+ String(p.product_name || '').toLowerCase().includes(term) ||
1693
+ String(p.product_sku || '').toLowerCase().includes(term),
1694
+ );
1695
+ }, [products, search]);
1696
+
1697
+ const accent = isPdvDevice
1698
+ ? (isOpen ? hex.success : hex.danger)
1699
+ : hex.info;
1700
+ const showPdvOperationTab =
1701
+ isPdvDevice && activePdvTab === PDV_TAB_OPERATION;
1702
+ const showPdvOrdersTab = isPdvDevice && activePdvTab === PDV_TAB_ORDERS;
1703
+ const showPdvDeviceTab = isPdvDevice && activePdvTab === PDV_TAB_DEVICE;
1704
+ const showPdvPaymentTypesTab =
1705
+ isPdvDevice && activePdvTab === PDV_TAB_PAYMENT_TYPES;
1706
+ const showPdvMovementTab =
1707
+ isPdvDevice && activePdvTab === PDV_TAB_MOVEMENT;
1708
+ const loadingActiveTabData = isPdvDevice && (
1709
+ (showPdvMovementTab && loadingMovementData) ||
1710
+ (!showPdvMovementTab && loadingConfigData) ||
1711
+ (showPdvOrdersTab && loadingCompanyDeviceConfigs)
1712
+ );
1713
+ const shouldShowOrderVisibility =
1714
+ !isDisplayDevice && (!isPdvDevice || showPdvOrdersTab);
1715
+ const shouldShowRemotePayment =
1716
+ shouldShowOrderVisibility &&
1717
+ (!hasLocalPaymentGateway || pdvGateway !== PAYMENT_GATEWAY_CIELO);
1718
+ const shouldShowDeviceBehavior = !isPdvDevice || showPdvDeviceTab;
1719
+ const shouldShowRemoteCommands =
1720
+ !isDisplayDevice && (!isPdvDevice || showPdvDeviceTab);
1721
+
1722
+ const renderProduct = ({ item, index }) => (
1723
+ <View style={[styles.productRow, index % 2 === 0 && styles.productRowAlt]}>
1724
+ <Text style={[styles.productCell, { flex: 0.5 }]}>{item.quantity}</Text>
1725
+ <View style={inlineStyle_667_12}>
1726
+ <Text style={styles.productName} numberOfLines={1}>{item.product_name}</Text>
1727
+ {!!item.product_sku && (
1728
+ <Text style={styles.productSku}>{item.product_sku}</Text>
1729
+ )}
1730
+ </View>
1731
+ <Text style={[styles.productCell, { flex: 1.2, textAlign: 'right' }]}>
1732
+ {Formatter.formatMoney(item.order_product_price)}
1733
+ </Text>
1734
+ <Text style={[styles.productCell, { flex: 1.3, textAlign: 'right', fontWeight: '700' }]}>
1735
+ {Formatter.formatMoney(item.order_product_total)}
1736
+ </Text>
1737
+ </View>
1738
+ );
1739
+
1740
+ const renderHelpButton = (title, message) => (
1741
+ <DefaultTooltip
1742
+ accentColor={themeColors.buttonIcon}
1743
+ title={title}
1744
+ message={message}
1745
+ style={{
1746
+ backgroundColor: themeColors.buttonBackground,
1747
+ borderColor: themeColors.buttonBackground,
1748
+ borderRadius: 8,
1749
+ height: 34,
1750
+ width: 34,
1751
+ }}
1752
+ textStyle={{
1753
+ color: themeColors.buttonIcon,
1754
+ fontSize: 16,
1755
+ lineHeight: 16,
1756
+ }}
1757
+ />
1758
+ );
1759
+
1760
+ const renderSwitchRow = ({
1761
+ disabled = false,
1762
+ label,
1763
+ onValueChange,
1764
+ value,
1765
+ valueLabel,
1766
+ }) => {
1767
+ const checked = Boolean(value);
1768
+
1769
+ return (
1770
+ <View
1771
+ style={[
1772
+ styles.toggleRow,
1773
+ {
1774
+ backgroundColor: themeColors.listItemBackground,
1775
+ borderColor: themeColors.listItemBorder,
1776
+ },
1777
+ disabled && styles.toggleRowDisabled,
1778
+ ]}>
1779
+ <View style={styles.toggleRowCopy}>
1780
+ <Text
1781
+ style={[
1782
+ styles.toggleRowLabel,
1783
+ {color: themeColors.listItemText},
1784
+ ]}>
1785
+ {label}
1786
+ </Text>
1787
+ <Text
1788
+ style={[
1789
+ styles.toggleRowValue,
1790
+ {color: themeColors.listItemSubtitleText},
1791
+ ]}>
1792
+ {valueLabel}
1793
+ </Text>
1794
+ </View>
1795
+ <Switch
1796
+ value={checked}
1797
+ disabled={disabled}
1798
+ onValueChange={onValueChange}
1799
+ {...getDeviceSwitchProps({
1800
+ disabled,
1801
+ palette: themeColors,
1802
+ value: checked,
1803
+ })}
1804
+ />
1805
+ </View>
1806
+ );
1807
+ };
1808
+
1809
+ const renderOptionButtons = ({ options, value, onChange, disabled = false, optionColors = null }) => (
1810
+ <View style={styles.optionRow}>
1811
+ {options.map(option => {
1812
+ const selected = String(option.value) === String(value);
1813
+ const optionDisabled = disabled || option.disabled === true;
1814
+ const optionTitle = String(option.title || '').trim();
1815
+ if (optionDisabled && optionTitle) {
1816
+ return (
1817
+ <OptionButtonChip
1818
+ key={String(option.value)}
1819
+ label={option.label}
1820
+ selected={selected}
1821
+ disabled
1822
+ colors={optionColors}
1823
+ tooltip={optionTitle}
1824
+ onPress={() => onChange(option.value)}
1825
+ />
1826
+ );
1827
+ }
1828
+
1829
+ return (
1830
+ <OptionButtonChip
1831
+ key={String(option.value)}
1832
+ label={option.label}
1833
+ selected={selected}
1834
+ disabled={optionDisabled}
1835
+ colors={optionColors}
1836
+ tooltip={optionTitle}
1837
+ onPress={() => onChange(option.value)}
1838
+ />
1839
+ );
1840
+ })}
1841
+ </View>
1842
+ );
1843
+
1844
+ return (
1845
+ <SafeAreaView style={[styles.container, { backgroundColor: brandColors.background }]}>
1846
+ <ScrollView contentContainerStyle={styles.scroll} showsVerticalScrollIndicator={false}>
1847
+
1848
+ {/* Cabeçalho do dispositivo */}
1849
+ <View style={styles.deviceHeader}>
1850
+ <View style={styles.deviceHeaderLeft}>
1851
+ <View style={[styles.deviceIconBox, { backgroundColor: withOpacity(accent, 0.1) }]}>
1852
+ <Icon name="monitor" size={20} color={accent} />
1853
+ </View>
1854
+
1855
+ <View style={styles.aliasBlock}>
1856
+ <View style={styles.aliasRow}>
1857
+ {editingAlias ? (
1858
+ <TextInput
1859
+ ref={aliasInputRef}
1860
+ style={styles.aliasInput}
1861
+ value={aliasInput}
1862
+ onChangeText={setAliasInput}
1863
+ onSubmitEditing={saveAlias}
1864
+ returnKeyType="done"
1865
+ autoCapitalize="words"
1866
+ selectTextOnFocus
1867
+ />
1868
+ ) : (
1869
+ <Text style={styles.deviceAlias} numberOfLines={1} ellipsizeMode="tail">
1870
+ {alias}
1871
+ </Text>
1872
+ )}
1873
+
1874
+ {!!deviceId && (
1875
+ <TouchableOpacity
1876
+ style={[
1877
+ styles.editAliasBtn,
1878
+ {
1879
+ backgroundColor: themeColors.buttonBackground,
1880
+ borderColor: themeColors.buttonBackground,
1881
+ },
1882
+ ]}
1883
+ onPress={editingAlias ? saveAlias : startEditAlias}
1884
+ activeOpacity={0.8}
1885
+ disabled={savingAlias}
1886
+ >
1887
+ <Icon
1888
+ name={savingAlias ? 'save' : editingAlias ? 'check' : 'edit-2'}
1889
+ size={16}
1890
+ color={themeColors.buttonIcon}
1891
+ />
1892
+ </TouchableOpacity>
1893
+ )}
1894
+ </View>
1895
+
1896
+ <Text style={styles.deviceString} numberOfLines={1} ellipsizeMode="middle">
1897
+ {deviceString}
1898
+ </Text>
1899
+ </View>
1900
+ </View>
1901
+
1902
+ </View>
1903
+
1904
+ {loadingActiveTabData && (
1905
+ <StateStore
1906
+ compact
1907
+ loading="Carregando dados do device..."
1908
+ />
1909
+ )}
1910
+
1911
+ {isPdvDevice && (
1912
+ <View style={styles.tabsBar}>
1913
+ <ScrollView
1914
+ horizontal
1915
+ showsHorizontalScrollIndicator={false}
1916
+ contentContainerStyle={styles.tabsContent}>
1917
+ {PDV_DETAIL_TABS.map(tab => {
1918
+ const active = activePdvTab === tab.key;
1919
+ const tabButtonColors = active
1920
+ ? {
1921
+ backgroundColor: themeColors.buttonBackground,
1922
+ borderColor: themeColors.buttonBorder,
1923
+ iconColor: themeColors.buttonIcon,
1924
+ textColor: themeColors.buttonText,
1925
+ }
1926
+ : {
1927
+ backgroundColor: themeColors.buttonBackgroundSecondary,
1928
+ borderColor: themeColors.buttonBorderSecondary,
1929
+ iconColor: themeColors.buttonIconSecondary,
1930
+ textColor: themeColors.buttonTextSecondary,
1931
+ };
1932
+
1933
+ return (
1934
+ <TouchableOpacity
1935
+ key={tab.key}
1936
+ style={[
1937
+ styles.tabButton,
1938
+ {
1939
+ backgroundColor: tabButtonColors.backgroundColor,
1940
+ borderColor: tabButtonColors.borderColor,
1941
+ },
1942
+ ]}
1943
+ activeOpacity={0.85}
1944
+ onPress={() => setActivePdvTab(tab.key)}>
1945
+ <Icon
1946
+ name={tab.icon}
1947
+ size={14}
1948
+ color={tabButtonColors.iconColor}
1949
+ />
1950
+ <Text
1951
+ style={[
1952
+ styles.tabButtonText,
1953
+ {color: tabButtonColors.textColor},
1954
+ ]}>
1955
+ {tt('tab', tab.labelKey) ||
1956
+ (tab.key === PDV_TAB_PAYMENT_TYPES
1957
+ ? 'Pagamentos'
1958
+ : tab.labelKey)}
1959
+ </Text>
1960
+ </TouchableOpacity>
1961
+ );
1962
+ })}
1963
+ </ScrollView>
1964
+ </View>
1965
+ )}
1966
+
1967
+ {showPdvMovementTab && (
1968
+ <View style={styles.summaryRow}>
1969
+ <View style={styles.summaryCard}>
1970
+ <Icon name="dollar-sign" size={14} color={hex.success} style={styles.summaryIcon} />
1971
+ <Text style={styles.summaryLabel}>Total Geral</Text>
1972
+ <Text style={[styles.summaryValue, { color: hex.success }]}>
1973
+ {Formatter.formatMoney(inflowTotal)}
1974
+ </Text>
1975
+ </View>
1976
+ <View style={styles.summaryCard}>
1977
+ <Icon name="shopping-bag" size={14} color={hex.info} style={styles.summaryIcon} />
1978
+ <Text style={styles.summaryLabel}>Em Produtos</Text>
1979
+ <Text style={[styles.summaryValue, { color: hex.info }]}>
1980
+ {Formatter.formatMoney(productTotal)}
1981
+ </Text>
1982
+ </View>
1983
+ <View style={styles.summaryCard}>
1984
+ <Icon name="package" size={14} color={hex.purple} style={styles.summaryIcon} />
1985
+ <Text style={styles.summaryLabel}>Itens</Text>
1986
+ <Text style={[styles.summaryValue, { color: hex.purple }]}>
1987
+ {products.length}
1988
+ </Text>
1989
+ </View>
1990
+ </View>
1991
+ )}
1992
+
1993
+ {showPdvOperationTab && (
1994
+ <View style={styles.section}>
1995
+ <Text style={styles.sectionTitle}>
1996
+ <Icon name="credit-card" size={13} /> {' '}Configuração do PDV
1997
+ </Text>
1998
+
1999
+ <View style={styles.configCard}>
2000
+ <View style={styles.sectionTitleRow}>
2001
+ <Text style={styles.configTitle}>
2002
+ {tt('title', 'posOperationMode')}
2003
+ </Text>
2004
+ {renderHelpButton(
2005
+ tt('title', 'posOperationMode') || 'Modo de operacao',
2006
+ tt('description', 'posOperationModeDescription') ||
2007
+ 'Escolha o modo, a trava kiosk e a politica de ordem/caixa deste device.',
2008
+ )}
2009
+ </View>
2010
+
2011
+ {renderOptionButtons({
2012
+ options: POS_OPERATION_MODE_OPTIONS.map(option => ({
2013
+ label: tt('option', option.translationKey),
2014
+ value: option.value,
2015
+ })),
2016
+ value: posOperationMode,
2017
+ optionColors: {
2018
+ buttonBackground: themeColors.buttonBackground,
2019
+ buttonBorder: themeColors.buttonBorder,
2020
+ buttonText: themeColors.buttonText,
2021
+ buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
2022
+ buttonBorderSecondary: themeColors.buttonBorderSecondary,
2023
+ buttonTextSecondary: themeColors.buttonTextSecondary,
2024
+ },
2025
+ onChange: value => {
2026
+ const nextValue = resolvePosOperationMode({
2027
+ [POS_OPERATION_MODE_CONFIG_KEY]: value,
2028
+ });
2029
+ setPosOperationMode(nextValue);
2030
+ savePosOperationMode({posOperationMode: nextValue});
2031
+ },
2032
+ })}
2033
+
2034
+ <View style={styles.pickerWrap}>
2035
+ <Picker
2036
+ selectedValue={productShowcaseId || ''}
2037
+ mode={pickerMode}
2038
+ enabled={!loadingProductShowcases && !savingProductShowcase}
2039
+ style={styles.picker}
2040
+ dropdownIconColor="#64748B"
2041
+ onValueChange={value => {
2042
+ const nextValue = normalizeEntityId(value);
2043
+ setProductShowcaseId(nextValue);
2044
+ saveProductShowcaseConfig({productShowcaseId: nextValue});
2045
+ }}>
2046
+ <Picker.Item
2047
+ label={
2048
+ loadingProductShowcases
2049
+ ? 'Carregando vitrines...'
2050
+ : 'Sem vitrine vinculada'
2051
+ }
2052
+ value=""
2053
+ />
2054
+ {productShowcases.map(showcase => {
2055
+ const showcaseId = normalizeEntityId(showcase);
2056
+ return (
2057
+ <Picker.Item
2058
+ key={`pos-showcase-${showcaseId}`}
2059
+ label={getProductShowcaseLabel(showcase)}
2060
+ value={showcaseId}
2061
+ />
2062
+ );
2063
+ })}
2064
+ </Picker>
2065
+ </View>
2066
+
2067
+ {renderSwitchRow({
2068
+ disabled: savingPosOperationMode,
2069
+ label: 'Modo Kiosk',
2070
+ value: androidKioskEnabled,
2071
+ valueLabel: androidKioskEnabled ? 'Ativo' : 'Inativo',
2072
+ onValueChange: nextValue => {
2073
+ setAndroidKioskEnabled(nextValue);
2074
+ savePosOperationMode({androidKioskEnabled: nextValue});
2075
+ },
2076
+ })}
2077
+
2078
+ {renderOptionButtons({
2079
+ options: [
2080
+ {
2081
+ label: global.t?.t('configs', 'option', 'none') || 'None',
2082
+ value: POS_CHECK_ORDER_TYPE_NONE,
2083
+ },
2084
+ {
2085
+ label: global.t?.t('orders', 'title', 'tab') || 'Tab',
2086
+ value: POS_CHECK_ORDER_TYPE_TAB,
2087
+ },
2088
+ {
2089
+ label: global.t?.t('orders', 'title', 'table') || 'Table',
2090
+ value: POS_CHECK_ORDER_TYPE_TABLE,
2091
+ },
2092
+ {
2093
+ label: global.t?.t('orders', 'title', 'stamp') || 'Stamp',
2094
+ value: POS_CHECK_ORDER_TYPE_STAMP,
2095
+ disabled: !loyaltyCouponsEnabled,
2096
+ title: !loyaltyCouponsEnabled
2097
+ ? 'Ative em Shop -> Cupom fidelidade\npara liberar Stamp'
2098
+ : '',
2099
+ },
2100
+ ],
2101
+ value: checkOrderType,
2102
+ optionColors: {
2103
+ buttonBackground: themeColors.buttonBackground,
2104
+ buttonBorder: themeColors.buttonBorder,
2105
+ buttonText: themeColors.buttonText,
2106
+ buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
2107
+ buttonBorderSecondary: themeColors.buttonBorderSecondary,
2108
+ buttonTextSecondary: themeColors.buttonTextSecondary,
2109
+ },
2110
+ onChange: value => {
2111
+ const nextCheckOrderType =
2112
+ value === POS_CHECK_ORDER_TYPE_TAB
2113
+ ? POS_CHECK_ORDER_TYPE_TAB
2114
+ : value === POS_CHECK_ORDER_TYPE_TABLE
2115
+ ? POS_CHECK_ORDER_TYPE_TABLE
2116
+ : value === POS_CHECK_ORDER_TYPE_STAMP
2117
+ ? loyaltyCouponsEnabled
2118
+ ? POS_CHECK_ORDER_TYPE_STAMP
2119
+ : POS_CHECK_ORDER_TYPE_NONE
2120
+ : POS_CHECK_ORDER_TYPE_NONE;
2121
+ const nextCheckOrderManagementMode =
2122
+ nextCheckOrderType === POS_CHECK_ORDER_TYPE_NONE
2123
+ ? POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE
2124
+ : checkOrderManagementMode;
2125
+ setCheckOrderType(nextCheckOrderType);
2126
+ if (nextCheckOrderType === POS_CHECK_ORDER_TYPE_NONE) {
2127
+ setCheckOrderManagementMode(
2128
+ POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE,
2129
+ );
2130
+ }
2131
+ savePosOperationMode({
2132
+ checkOrderType: nextCheckOrderType,
2133
+ checkOrderManagementMode: nextCheckOrderManagementMode,
2134
+ });
2135
+ },
2136
+ })}
2137
+
2138
+ {checkOrderType !== POS_CHECK_ORDER_TYPE_NONE &&
2139
+ renderOptionButtons({
2140
+ options: [
2141
+ {
2142
+ label:
2143
+ global.t?.t(
2144
+ 'configs',
2145
+ 'option',
2146
+ 'manageLinkedOrders',
2147
+ ) || 'Open and close tabs/tables/stamps',
2148
+ value: POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE,
2149
+ },
2150
+ {
2151
+ label:
2152
+ global.t?.t(
2153
+ 'configs',
2154
+ 'option',
2155
+ 'existingLinkedOrdersOnly',
2156
+ ) || 'Use open tabs/tables/stamps only',
2157
+ value: POS_CHECK_ORDER_MANAGEMENT_MODE_EXISTING_ONLY,
2158
+ },
2159
+ ],
2160
+ value: checkOrderManagementMode,
2161
+ onChange: value => {
2162
+ const nextValue =
2163
+ value === POS_CHECK_ORDER_MANAGEMENT_MODE_EXISTING_ONLY
2164
+ ? POS_CHECK_ORDER_MANAGEMENT_MODE_EXISTING_ONLY
2165
+ : POS_CHECK_ORDER_MANAGEMENT_MODE_MANAGE;
2166
+ setCheckOrderManagementMode(nextValue);
2167
+ savePosOperationMode({checkOrderManagementMode: nextValue});
2168
+ },
2169
+ })}
2170
+
2171
+ {posOperationMode === POS_OPERATION_MODE_COUNTER && (
2172
+ <>
2173
+ {renderSwitchRow({
2174
+ disabled: savingPosOperationMode,
2175
+ label: 'Impressao automatica',
2176
+ value: counterAutoPrintEnabled,
2177
+ valueLabel: counterAutoPrintEnabled ? 'Sim' : 'Nao',
2178
+ onValueChange: nextValue => {
2179
+ setCounterAutoPrintEnabled(nextValue);
2180
+ savePosOperationMode({counterAutoPrintEnabled: nextValue});
2181
+ },
2182
+ })}
2183
+
2184
+ {counterAutoPrintEnabled &&
2185
+ renderOptionButtons({
2186
+ options: [
2187
+ {label: 'Pedido', value: POS_PRINT_MODE_ORDER},
2188
+ {label: 'Fichas', value: POS_PRINT_MODE_FORM},
2189
+ ],
2190
+ value: counterPrintMode,
2191
+ optionColors: {
2192
+ buttonBackground: themeColors.buttonBackground,
2193
+ buttonBorder: themeColors.buttonBorder,
2194
+ buttonText: themeColors.buttonText,
2195
+ buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
2196
+ buttonBorderSecondary: themeColors.buttonBorderSecondary,
2197
+ buttonTextSecondary: themeColors.buttonTextSecondary,
2198
+ },
2199
+ onChange: value => {
2200
+ const nextValue =
2201
+ value === POS_PRINT_MODE_FORM
2202
+ ? POS_PRINT_MODE_FORM
2203
+ : POS_PRINT_MODE_ORDER;
2204
+ setCounterPrintMode(nextValue);
2205
+ savePosOperationMode({counterPrintMode: nextValue});
2206
+ },
2207
+ })}
2208
+
2209
+ {renderOptionButtons({
2210
+ options: [
2211
+ {
2212
+ label: 'Abertura e fechamento de caixa',
2213
+ value: POS_CASH_MANAGEMENT_MODE_CASH_REGISTER,
2214
+ },
2215
+ {
2216
+ label: 'Fechamento diario',
2217
+ value: POS_CASH_MANAGEMENT_MODE_DAILY,
2218
+ },
2219
+ ],
2220
+ value: counterCashManagementMode,
2221
+ optionColors: {
2222
+ buttonBackground: themeColors.buttonBackground,
2223
+ buttonBorder: themeColors.buttonBorder,
2224
+ buttonText: themeColors.buttonText,
2225
+ buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
2226
+ buttonBorderSecondary: themeColors.buttonBorderSecondary,
2227
+ buttonTextSecondary: themeColors.buttonTextSecondary,
2228
+ },
2229
+ onChange: value => {
2230
+ const nextValue =
2231
+ value === POS_CASH_MANAGEMENT_MODE_DAILY
2232
+ ? POS_CASH_MANAGEMENT_MODE_DAILY
2233
+ : POS_CASH_MANAGEMENT_MODE_CASH_REGISTER;
2234
+ setCounterCashManagementMode(nextValue);
2235
+ savePosOperationMode({counterCashManagementMode: nextValue});
2236
+ },
2237
+ })}
2238
+ </>
2239
+ )}
2240
+ </View>
2241
+
2242
+ <View style={styles.configCard}>
2243
+ <View style={styles.sectionTitleRow}>
2244
+ <Text style={[styles.configTitle, {color: brandColors.text}]}>
2245
+ {tt('title', 'androidLauncherMode') || 'Launcher / home app'}
2246
+ </Text>
2247
+ {renderHelpButton(
2248
+ tt('title', 'androidLauncherMode') || 'Launcher / home app',
2249
+ tt('description', 'androidLauncherDescription') ||
2250
+ 'Quando ativado, o device volta para a app ao usar home ou apps recentes. O voltar continua seguindo a tela.',
2251
+ )}
2252
+ </View>
2253
+
2254
+ {renderSwitchRow({
2255
+ disabled: savingLauncherMode,
2256
+ label: 'Modo launcher?',
2257
+ value: androidLauncherEnabled,
2258
+ valueLabel: androidLauncherEnabled ? 'Ativo' : 'Inativo',
2259
+ onValueChange: nextValue => {
2260
+ setAndroidLauncherEnabled(nextValue);
2261
+ saveLauncherMode({androidLauncherEnabled: nextValue});
2262
+ },
2263
+ })}
2264
+ <Text style={[styles.deviceString, {color: brandColors.textSecondary}]}>
2265
+ Salva automaticamente
2266
+ </Text>
2267
+ </View>
2268
+
2269
+ <View style={styles.configCard}>
2270
+ <View style={styles.sectionTitleRow}>
2271
+ <Text style={styles.configTitle}>Gateway e impressora</Text>
2272
+ {renderHelpButton(
2273
+ 'Gateway e impressora',
2274
+ 'Escolha o gateway usado pelo PDV e se ele pode aparecer como destino de impressao.',
2275
+ )}
2276
+ </View>
2277
+
2278
+ {renderOptionButtons({
2279
+ options: [
2280
+ {label: 'Nenhum', value: ''},
2281
+ {label: 'Infinite Pay', value: 'infinite-pay'},
2282
+ {label: 'Cielo', value: 'cielo'},
2283
+ ],
2284
+ value: pdvGateway || '',
2285
+ optionColors: {
2286
+ buttonBackground: themeColors.buttonBackground,
2287
+ buttonBorder: themeColors.buttonBorder,
2288
+ buttonText: themeColors.buttonText,
2289
+ buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
2290
+ buttonBorderSecondary: themeColors.buttonBorderSecondary,
2291
+ buttonTextSecondary: themeColors.buttonTextSecondary,
2292
+ },
2293
+ onChange: value => {
2294
+ const nextValue = String(value || '');
2295
+ setPdvGateway(nextValue);
2296
+ savePdvSettings({pdvGateway: nextValue});
2297
+ },
2298
+ })}
2299
+
2300
+ {renderSwitchRow({
2301
+ disabled: savingPdvSettings,
2302
+ label: 'Impressora',
2303
+ value: pdvPrinterEnabled,
2304
+ valueLabel: pdvPrinterEnabled ? 'Sim' : 'Nao',
2305
+ onValueChange: nextValue => {
2306
+ setPdvPrinterEnabled(nextValue);
2307
+ savePdvSettings({pdvPrinterEnabled: nextValue});
2308
+ },
2309
+ })}
2310
+ <Text style={styles.deviceString}>Salva automaticamente</Text>
2311
+ </View>
2312
+ </View>
2313
+ )}
2314
+
2315
+ {shouldShowOrderVisibility && (
2316
+ <View style={styles.section}>
2317
+ <Text style={styles.sectionTitle}>
2318
+ <Icon name="list" size={13} /> {' '}Pedidos do Device
2319
+ </Text>
2320
+
2321
+ <View style={styles.configCard}>
2322
+ <View style={styles.sectionTitleRow}>
2323
+ <Text style={styles.configTitle}>Escopo da listagem no PDV</Text>
2324
+ {renderHelpButton(
2325
+ 'Escopo da listagem',
2326
+ 'Define se este device mostra apenas os pedidos criados nele ou todos os pedidos da empresa.',
2327
+ )}
2328
+ </View>
2329
+
2330
+ {renderOptionButtons({
2331
+ options: [
2332
+ {
2333
+ label: 'Somente deste device',
2334
+ value: DEVICE_ORDER_VISIBILITY_DEVICE,
2335
+ },
2336
+ {
2337
+ label: 'Todos da empresa',
2338
+ value: DEVICE_ORDER_VISIBILITY_COMPANY,
2339
+ },
2340
+ ],
2341
+ value: deviceOrderVisibility,
2342
+ optionColors: {
2343
+ buttonBackground: themeColors.buttonBackground,
2344
+ buttonBorder: themeColors.buttonBorder,
2345
+ buttonText: themeColors.buttonText,
2346
+ buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
2347
+ buttonBorderSecondary: themeColors.buttonBorderSecondary,
2348
+ buttonTextSecondary: themeColors.buttonTextSecondary,
2349
+ },
2350
+ onChange: value => {
2351
+ const nextValue =
2352
+ value || DEVICE_ORDER_VISIBILITY_DEVICE;
2353
+ setDeviceOrderVisibility(nextValue);
2354
+ saveDeviceOrderVisibility({deviceOrderVisibility: nextValue});
2355
+ },
2356
+ })}
2357
+ </View>
2358
+
2359
+ <View style={styles.configCard}>
2360
+ <View style={styles.sectionTitleRow}>
2361
+ <Text style={styles.configTitle}>
2362
+ {tt('title', 'deliveryOnDevice') || 'Delivery neste equipamento'}
2363
+ </Text>
2364
+ {renderHelpButton(
2365
+ tt('title', 'deliveryOnDevice') || 'Delivery neste equipamento',
2366
+ tt('description', 'deliveryOnDeviceDescription') ||
2367
+ 'Ative quando este equipamento precisa operar pedidos com cliente, endereço e observações de entrega.',
2368
+ )}
2369
+ </View>
2370
+
2371
+ {renderSwitchRow({
2372
+ disabled: savingDeviceDeliverySettings,
2373
+ label: tt('label', 'deliveryEnabled') ||
2374
+ 'Trabalhar com delivery',
2375
+ value: deviceDeliveryEnabled,
2376
+ valueLabel: deviceDeliveryEnabled ? 'Ativo' : 'Inativo',
2377
+ onValueChange: nextValue => {
2378
+ setDeviceDeliveryEnabled(nextValue);
2379
+ saveDeviceDeliverySettings({
2380
+ deviceDeliveryEnabled: nextValue,
2381
+ });
2382
+ },
2383
+ })}
2384
+ </View>
2385
+ </View>
2386
+ )}
2387
+
2388
+ {isDisplayDevice && (
2389
+ <View style={styles.section}>
2390
+ <Text style={styles.sectionTitle}>
2391
+ <Icon name="printer" size={13} /> {' '}Impressão de Preparo
2392
+ </Text>
2393
+
2394
+ <View style={styles.configCard}>
2395
+ <View style={styles.sectionTitleRow}>
2396
+ <Text style={styles.configTitle}>Display vinculado e impressora da fila</Text>
2397
+ {renderHelpButton(
2398
+ 'Display vinculado e impressora da fila',
2399
+ 'Este bloco é usado na impressão automática disparada pelo app DISPLAY. O device DISPLAY precisa apontar qual display representa e qual impressora deve receber a cópia separada por fila.',
2400
+ )}
2401
+ </View>
2402
+
2403
+ {(isLoadingDisplays || isLoadingPrinters) ? (
2404
+ <StateStore
2405
+ compact
2406
+ loading="Carregando displays e impressoras..."
2407
+ />
2408
+ ) : (
2409
+ <>
2410
+ <View style={styles.pickerWrap}>
2411
+ <Picker
2412
+ selectedValue={linkedDisplayId || ''}
2413
+ mode={pickerMode}
2414
+ style={styles.picker}
2415
+ dropdownIconColor="#64748B"
2416
+ onValueChange={value => {
2417
+ const nextValue = String(value || '').trim();
2418
+ setLinkedDisplayId(nextValue);
2419
+ saveDisplayPrintingConfig({linkedDisplayId: nextValue});
2420
+ }}>
2421
+ <Picker.Item
2422
+ label="Nenhum display vinculado"
2423
+ value=""
2424
+ />
2425
+ {displayOptions.map(option => {
2426
+ const optionId = normalizeEntityId(option);
2427
+ return (
2428
+ <Picker.Item
2429
+ key={`display-option-${optionId}`}
2430
+ label={getDisplayLabel(option)}
2431
+ value={optionId}
2432
+ />
2433
+ );
2434
+ })}
2435
+ </Picker>
2436
+ </View>
2437
+
2438
+ <View style={styles.pickerWrap}>
2439
+ <Picker
2440
+ selectedValue={displayPrinterId || ''}
2441
+ mode={pickerMode}
2442
+ style={styles.picker}
2443
+ dropdownIconColor="#64748B"
2444
+ onValueChange={value => {
2445
+ const nextValue = normalizeDeviceId(value);
2446
+ setDisplayPrinterId(nextValue);
2447
+ saveDisplayPrintingConfig({
2448
+ displayPrinterId: nextValue,
2449
+ });
2450
+ }}>
2451
+ <Picker.Item
2452
+ label="Nenhuma impressora configurada"
2453
+ value=""
2454
+ />
2455
+ {printerOptions.map(option => {
2456
+ const printerId = normalizeDeviceId(option?.device);
2457
+ const printerValue = getPrinterOptionValue(option);
2458
+ const printerTypeLabel = getDeviceTypeLabel(
2459
+ option?.type,
2460
+ );
2461
+ return (
2462
+ <Picker.Item
2463
+ key={`printer-option-${printerValue || printerId}`}
2464
+ label={`${getPrinterLabel(option)} (${printerTypeLabel} • ${printerId})`}
2465
+ value={printerValue || printerId}
2466
+ />
2467
+ );
2468
+ })}
2469
+ </Picker>
2470
+ </View>
2471
+
2472
+ {renderSwitchRow({
2473
+ disabled: savingDisplayPrintingConfig,
2474
+ label: 'Pode trocar de impressora?',
2475
+ value: displayAllowPrinterChange,
2476
+ valueLabel: displayAllowPrinterChange ? 'Sim' : 'Nao',
2477
+ onValueChange: nextValue => {
2478
+ setDisplayAllowPrinterChange(nextValue);
2479
+ saveDisplayPrintingConfig({
2480
+ displayAllowPrinterChange: nextValue,
2481
+ });
2482
+ },
2483
+ })}
2484
+
2485
+ {renderSwitchRow({
2486
+ disabled: savingDisplayPrintingConfig,
2487
+ label: 'Imprimir produtos automaticamente',
2488
+ value: displayAutoPrintProductEnabled,
2489
+ valueLabel: displayAutoPrintProductEnabled
2490
+ ? 'Ativo'
2491
+ : 'Inativo',
2492
+ onValueChange: nextValue => {
2493
+ setDisplayAutoPrintProductEnabled(nextValue);
2494
+ saveDisplayPrintingConfig({
2495
+ displayAutoPrintProductEnabled: nextValue,
2496
+ });
2497
+ },
2498
+ })}
2499
+ </>
2500
+ )}
2501
+ </View>
2502
+ </View>
2503
+ )}
2504
+
2505
+ {shouldShowDeviceBehavior && (
2506
+ <View style={styles.section}>
2507
+ <Text style={styles.sectionTitle}>
2508
+ <Icon name="volume-2" size={13} /> {' '}Aviso Sonoro
2509
+ </Text>
2510
+
2511
+ <View style={styles.configCard}>
2512
+ <View style={styles.sectionTitleRow}>
2513
+ <Text style={styles.configTitle}>Alerta via websocket</Text>
2514
+ {renderHelpButton(
2515
+ 'Alerta via websocket',
2516
+ 'Quando habilitado, este device toca o audio configurado ao receber o evento order.created de um novo pedido em preparo.',
2517
+ )}
2518
+ </View>
2519
+
2520
+ {renderSwitchRow({
2521
+ disabled: savingAlertSound,
2522
+ label: 'Aviso sonoro habilitado',
2523
+ value: deviceAlertSoundEnabled,
2524
+ valueLabel: deviceAlertSoundEnabled ? 'Ativo' : 'Inativo',
2525
+ onValueChange: nextValue => {
2526
+ setDeviceAlertSoundEnabled(nextValue);
2527
+ saveDeviceAlertSoundConfig({
2528
+ deviceAlertSoundEnabled: nextValue,
2529
+ });
2530
+ },
2531
+ })}
2532
+
2533
+ <View style={styles.textInputWrap}>
2534
+ <Text style={styles.textInputLabel}>URL do audio</Text>
2535
+ <TextInput
2536
+ style={styles.textInput}
2537
+ value={deviceAlertSoundUrl}
2538
+ onChangeText={setDeviceAlertSoundUrl}
2539
+ placeholder="https://exemplo.com/alerta.mp3"
2540
+ placeholderTextColor="#94A3B8"
2541
+ autoCapitalize="none"
2542
+ autoCorrect={false}
2543
+ keyboardType="url"
2544
+ returnKeyType="done"
2545
+ onSubmitEditing={saveDeviceAlertSoundConfig}
2546
+ onBlur={saveDeviceAlertSoundConfig}
2547
+ />
2548
+ </View>
2549
+ </View>
2550
+ </View>
2551
+ )}
2552
+
2553
+ {shouldShowDeviceBehavior && (
2554
+ <View style={styles.section}>
2555
+ <Text style={styles.sectionTitle}>
2556
+ <Icon name="activity" size={13} /> {' '}Rodapé do Sistema
2557
+ </Text>
2558
+
2559
+ <View style={styles.configCard}>
2560
+ <View style={styles.sectionTitleRow}>
2561
+ <Text style={styles.configTitle}>Debug do socket no rodapé</Text>
2562
+ {renderHelpButton(
2563
+ 'Debug do socket no rodapé',
2564
+ 'Quando habilitado, este device troca a bolinha discreta do socket pelos detalhes de debug publicados pelos serviços do runtime no rodapé global do sistema.',
2565
+ )}
2566
+ </View>
2567
+
2568
+ {renderSwitchRow({
2569
+ disabled: savingRuntimeDebugInfo,
2570
+ label: 'Exibir debug detalhado',
2571
+ value: deviceRuntimeDebugInfoEnabled,
2572
+ valueLabel: deviceRuntimeDebugInfoEnabled ? 'Ativo' : 'Inativo',
2573
+ onValueChange: nextValue => {
2574
+ setDeviceRuntimeDebugInfoEnabled(nextValue);
2575
+ saveDeviceRuntimeDebugInfo({
2576
+ deviceRuntimeDebugInfoEnabled: nextValue,
2577
+ });
2578
+ },
2579
+ })}
2580
+ </View>
2581
+ </View>
2582
+ )}
2583
+
2584
+ {shouldShowRemotePayment && (
2585
+ <View style={styles.section}>
2586
+ <Text style={styles.sectionTitle}>
2587
+ <Icon name="credit-card" size={13} /> {' '}Pagamento Remoto
2588
+ </Text>
2589
+
2590
+ <View style={styles.configCard}>
2591
+ <View style={styles.sectionTitleRow}>
2592
+ <Text style={styles.configTitle}>Device preferencial para pagamento</Text>
2593
+ {renderHelpButton(
2594
+ 'Device preferencial para pagamento',
2595
+ 'Esse destino funciona como fallback desta origem quando a empresa não definiu uma ordem padrão no configurador geral. Quando a empresa tiver devices padrão para pagamento remoto, essa ordem global tem prioridade.',
2596
+ )}
2597
+ </View>
2598
+
2599
+ <View style={styles.pickerWrap}>
2600
+ <Picker
2601
+ selectedValue={devicePaymentTarget || ''}
2602
+ mode={pickerMode}
2603
+ style={styles.picker}
2604
+ dropdownIconColor="#64748B"
2605
+ onValueChange={value => {
2606
+ const nextValue = value || '';
2607
+ setDevicePaymentTarget(nextValue);
2608
+ saveDevicePaymentTarget({
2609
+ devicePaymentTarget: nextValue,
2610
+ });
2611
+ }}>
2612
+ <Picker.Item
2613
+ label="Usar devices padrão da empresa"
2614
+ value=""
2615
+ />
2616
+ {paymentDeviceOptions.map(option => (
2617
+ <Picker.Item
2618
+ key={option.deviceId}
2619
+ label={`${option.alias} (${option.gatewayLabel})`}
2620
+ value={option.deviceId}
2621
+ />
2622
+ ))}
2623
+ </Picker>
2624
+ </View>
2625
+ </View>
2626
+ </View>
2627
+ )}
2628
+
2629
+ {shouldShowRemoteCommands && (
2630
+ <View style={styles.section}>
2631
+ <Text style={styles.sectionTitle}>
2632
+ <Icon name="refresh-cw" size={13} /> {' '}Comandos Remotos
2633
+ </Text>
2634
+
2635
+ <View style={styles.configCard}>
2636
+ <View style={styles.sectionTitleRow}>
2637
+ <Text style={styles.configTitle}>Catálogo do PDV</Text>
2638
+ {renderHelpButton(
2639
+ 'Catálogo do PDV',
2640
+ 'Limpa o cache local de produtos e categorias deste device. O recarregamento acontece no próximo uso do PDV.',
2641
+ )}
2642
+ </View>
2643
+
2644
+ <TouchableOpacity
2645
+ style={[
2646
+ styles.configButton,
2647
+ {
2648
+ backgroundColor: themeColors.buttonBackground,
2649
+ borderColor: themeColors.buttonBackground,
2650
+ },
2651
+ sendingCatalogRefresh && {opacity: 0.6},
2652
+ ]}
2653
+ activeOpacity={0.85}
2654
+ disabled={sendingCatalogRefresh}
2655
+ onPress={sendCatalogRefreshCommand}>
2656
+ <Icon name="trash-2" size={16} color={themeColors.buttonIcon} />
2657
+ <Text
2658
+ style={[
2659
+ styles.configButtonText,
2660
+ {color: themeColors.buttonText},
2661
+ ]}>
2662
+ {sendingCatalogRefresh ? 'Limpando cache...' : 'Limpar cache de produtos'}
2663
+ </Text>
2664
+ </TouchableOpacity>
2665
+ </View>
2666
+ </View>
2667
+ )}
2668
+
2669
+ {showPdvPaymentTypesTab && (
2670
+ <View style={styles.section}>
2671
+ <View style={styles.sectionHeaderRow}>
2672
+ <Text style={styles.sectionTitle}>
2673
+ <Icon name="credit-card" size={13} /> {' '}Pagamentos do device
2674
+ </Text>
2675
+ {renderHelpButton(
2676
+ 'Pagamentos do device',
2677
+ 'Selecione os meios de pagamento que este device pode exibir e usar nas opções de pagamento. Os wallets entram só para organizar a lista.',
2678
+ )}
2679
+ </View>
2680
+ <PaymentTypesByWalletTab
2681
+ currentCompanyId={currentCompany?.id}
2682
+ configs={configs}
2683
+ disableSelection={savingPaymentTypes}
2684
+ isSaving={savingPaymentTypes}
2685
+ onPersistSelectedPaymentTypeIds={savePaymentTypeConfigs}
2686
+ />
2687
+ </View>
2688
+ )}
2689
+
2690
+ {showPdvMovementTab && (
2691
+ <View style={styles.section}>
2692
+ <View style={styles.sectionHeaderRow}>
2693
+ <Text style={styles.sectionTitle}>
2694
+ <Icon name="shield" size={13} /> {' '}
2695
+ {global.t?.t('manager', 'title', 'pdvMovement') || 'PDV Movement'}
2696
+ </Text>
2697
+ <TouchableOpacity
2698
+ style={[
2699
+ styles.toggleBtn,
2700
+ {backgroundColor: isOpen ? hex.danger : hex.success},
2701
+ actionLoading && {opacity: 0.6},
2702
+ ]}
2703
+ onPress={handleToggle}
2704
+ disabled={actionLoading || loadingConfigData}
2705
+ activeOpacity={0.85}>
2706
+ <Icon
2707
+ name={isOpen ? 'lock' : 'unlock'}
2708
+ size={13}
2709
+ color="#fff"
2710
+ />
2711
+ <Text style={styles.toggleBtnText}>
2712
+ {actionLoading
2713
+ ? isOpen
2714
+ ? 'Fechando...'
2715
+ : 'Abrindo...'
2716
+ : isOpen
2717
+ ? global.t?.t('orders', 'button', 'closeCashRegister') || 'Close'
2718
+ : global.t?.t('orders', 'button', 'openCashRegister') || 'Open'}
2719
+ </Text>
2720
+ </TouchableOpacity>
2721
+ </View>
2722
+ </View>
2723
+ )}
2724
+
2725
+ {showPdvMovementTab && wallets.length > 0 && (
2726
+ <View style={styles.section}>
2727
+ <Text style={styles.sectionTitle}>
2728
+ <Icon name="credit-card" size={13} /> {' '}Recebimentos por Forma de Pagamento
2729
+ </Text>
2730
+ {wallets.map((wallet, wi) => (
2731
+ <View key={wi} style={styles.walletCard}>
2732
+ <View style={styles.walletHeader}>
2733
+ <Icon name="briefcase" size={13} color="#64748B" />
2734
+ <Text style={styles.walletName}>{wallet.wallet || 'Carteira'}</Text>
2735
+ <Text style={[styles.walletTotal, { color: brandColors.primary }]}>
2736
+ {Formatter.formatMoney(wallet.total)}
2737
+ </Text>
2738
+ </View>
2739
+ {wallet.payments.map((pt, pi) => (
2740
+ <View key={pi} style={styles.paymentRow}>
2741
+ <View style={[styles.paymentIconBox, { backgroundColor: withOpacity(hex.info, 0.1) }]}>
2742
+ <Icon name={paymentIcon(pt.payment)} size={11} color={hex.info} />
2743
+ </View>
2744
+ <Text style={styles.paymentName}>{pt.payment || '-'}</Text>
2745
+ <Text style={styles.paymentValue}>
2746
+ {Formatter.formatMoney(pt.inflow)}
2747
+ </Text>
2748
+ </View>
2749
+ ))}
2750
+ </View>
2751
+ ))}
2752
+ </View>
2753
+ )}
2754
+
2755
+ {showPdvMovementTab && (
2756
+ <View style={styles.section}>
2757
+ <Text style={styles.sectionTitle}>
2758
+ <Icon name="shopping-bag" size={13} /> {' '}Produtos Vendidos
2759
+ </Text>
2760
+
2761
+ <View style={styles.searchRow}>
2762
+ <Icon name="search" size={14} color="#94A3B8" />
2763
+ <TextInput
2764
+ style={styles.searchInput}
2765
+ value={search}
2766
+ onChangeText={setSearch}
2767
+ placeholder="Buscar produto ou SKU..."
2768
+ placeholderTextColor="#94A3B8"
2769
+ />
2770
+ {!!search && (
2771
+ <TouchableOpacity onPress={() => setSearch('')}>
2772
+ <Icon name="x" size={14} color="#94A3B8" />
2773
+ </TouchableOpacity>
2774
+ )}
2775
+ </View>
2776
+
2777
+ {filteredProducts.length > 0 ? (
2778
+ <View style={styles.tableContainer}>
2779
+ <View style={styles.tableHeader}>
2780
+ <Text style={[styles.tableHead, { flex: 0.5 }]}>Qtd</Text>
2781
+ <Text style={[styles.tableHead, { flex: 3 }]}>Produto</Text>
2782
+ <Text style={[styles.tableHead, { flex: 1.2, textAlign: 'right' }]}>Unit.</Text>
2783
+ <Text style={[styles.tableHead, { flex: 1.3, textAlign: 'right' }]}>Total</Text>
2784
+ </View>
2785
+ <FlatList
2786
+ data={filteredProducts}
2787
+ keyExtractor={(item, i) => `${item.product_sku || i}`}
2788
+ renderItem={renderProduct}
2789
+ scrollEnabled={false}
2790
+ />
2791
+ <View style={styles.tableFooter}>
2792
+ <Text style={styles.tableFooterLabel}>Total em produtos</Text>
2793
+ <Text style={[styles.tableFooterValue, { color: brandColors.primary }]}>
2794
+ {Formatter.formatMoney(productTotal)}
2795
+ </Text>
2796
+ </View>
2797
+ </View>
2798
+ ) : (
2799
+ <View style={styles.emptyBox}>
2800
+ <Icon name="inbox" size={24} color="#CBD5E1" style={inlineStyle_1301_61} />
2801
+ <Text style={styles.emptyText}>
2802
+ {search ? 'Nenhum produto encontrado para esta busca' : 'Nenhum produto registrado neste equipamento'}
2803
+ </Text>
2804
+ </View>
2805
+ )}
2806
+ </View>
2807
+ )}
2808
+
2809
+ </ScrollView>
2810
+ </SafeAreaView>
2811
+ );
2812
+ };
2813
+
2814
+ export default DeviceDetailPage;