@controleonline/ui-common 1.2.71 → 1.2.76

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