@controleonline/ui-common 1.2.70

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 (203) hide show
  1. package/.github/agents/developer.agent.md +30 -0
  2. package/.github/agents/devops.agent.md +30 -0
  3. package/.github/agents/qa.agent.md +30 -0
  4. package/.github/agents/security.agent.md +30 -0
  5. package/.github/workflows/npmjs.yml +35 -0
  6. package/.github/workflows/pull-request-checks.yml +19 -0
  7. package/.scrutinizer.yml +24 -0
  8. package/FUNDING.yml +1 -0
  9. package/package.json +28 -0
  10. package/src/api/fetch.js +74 -0
  11. package/src/api/index.js +155 -0
  12. package/src/api/localDB.js +131 -0
  13. package/src/api/queue.js +66 -0
  14. package/src/react/assets/tap.mp3 +0 -0
  15. package/src/react/components/AddImportModal.js +157 -0
  16. package/src/react/components/AddImportModal.styles.js +81 -0
  17. package/src/react/components/BackgroundRuntimeBridge.js +219 -0
  18. package/src/react/components/BottomNavigationBar.config.js +238 -0
  19. package/src/react/components/BottomNavigationBar.js +120 -0
  20. package/src/react/components/BottomNavigationBar.styles.js +110 -0
  21. package/src/react/components/CategoryForm.js +572 -0
  22. package/src/react/components/CategoryForm.styles.js +334 -0
  23. package/src/react/components/ConfirmModal.js +24 -0
  24. package/src/react/components/ConfirmModal.styles.js +43 -0
  25. package/src/react/components/ContextHelpButton.js +69 -0
  26. package/src/react/components/ContextHelpButton.styles.js +62 -0
  27. package/src/react/components/DefaultProvider.native.js +848 -0
  28. package/src/react/components/DefaultProvider.styles.js +21 -0
  29. package/src/react/components/DefaultProvider.web.js +909 -0
  30. package/src/react/components/DeliveryPushBridge.native.js +314 -0
  31. package/src/react/components/DeliveryPushBridge.web.js +5 -0
  32. package/src/react/components/DeviceAlertSoundService.js +499 -0
  33. package/src/react/components/EntityLogContent.js +909 -0
  34. package/src/react/components/EntityLogContent.styles.js +400 -0
  35. package/src/react/components/EventBus.js +12 -0
  36. package/src/react/components/KioskModeBridge.js +65 -0
  37. package/src/react/components/LauncherModeBridge.js +47 -0
  38. package/src/react/components/LinkedOrderProductsTab.js +388 -0
  39. package/src/react/components/LinkedOrderProductsTab.styles.js +180 -0
  40. package/src/react/components/ManagerPushBridge.native.js +345 -0
  41. package/src/react/components/MessageService.js +314 -0
  42. package/src/react/components/Paywall/Google.js +89 -0
  43. package/src/react/components/PrintService.js +778 -0
  44. package/src/react/components/ProductCatalogCacheService.js +45 -0
  45. package/src/react/components/RemoteCheckoutService.js +292 -0
  46. package/src/react/components/RuntimeBottomNavigationBar.js +151 -0
  47. package/src/react/components/RuntimeInfoFooter.js +214 -0
  48. package/src/react/components/RuntimeInfoFooter.styles.js +56 -0
  49. package/src/react/components/SystemErrorToast.js +29 -0
  50. package/src/react/components/SystemErrorToast.styles.js +38 -0
  51. package/src/react/components/TouchFeedbackProvider.js +65 -0
  52. package/src/react/components/UnifiedPaymentBar.js +155 -0
  53. package/src/react/components/UnifiedPaymentBar.styles.js +174 -0
  54. package/src/react/components/WebsocketListener.native.js +487 -0
  55. package/src/react/components/WebsocketListener.web.js +102 -0
  56. package/src/react/components/inputs/IdInput.js +49 -0
  57. package/src/react/components/inputs/IdInput.styles.js +26 -0
  58. package/src/react/components/inputs/InvoiceDueDateFilterInput.js +74 -0
  59. package/src/react/components/inputs/InvoiceDueDateFilterInput.styles.js +34 -0
  60. package/src/react/components/inputs/InvoiceDueDateRangeFilterInput.js +115 -0
  61. package/src/react/components/inputs/InvoiceDueDateRangeFilterInput.styles.js +39 -0
  62. package/src/react/components/lists/CategoriesList.js +56 -0
  63. package/src/react/components/lists/PaymentTypeList.js +55 -0
  64. package/src/react/components/lists/ReceiverList.js +56 -0
  65. package/src/react/components/lists/SelectList.styles.js +18 -0
  66. package/src/react/components/lists/StatusList.js +55 -0
  67. package/src/react/components/lists/WalletList.js +54 -0
  68. package/src/react/components/radio/Spotify.js +144 -0
  69. package/src/react/components/radio/Spotify.styles.js +32 -0
  70. package/src/react/components/radio/YouTube.js +82 -0
  71. package/src/react/components/radio/YouTube.styles.js +16 -0
  72. package/src/react/components/toastConfig.js +16 -0
  73. package/src/react/config/deviceConfigBootstrap.js +571 -0
  74. package/src/react/index.js +4 -0
  75. package/src/react/localStorage.js +30 -0
  76. package/src/react/pages/EntityLogPage.js +124 -0
  77. package/src/react/pages/EntityLogPage.styles.js +17 -0
  78. package/src/react/pages/GenericLogPage.js +760 -0
  79. package/src/react/pages/GenericLogPage.styles.js +401 -0
  80. package/src/react/pages/Imports.js +361 -0
  81. package/src/react/pages/Imports.styles.js +223 -0
  82. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +462 -0
  83. package/src/react/pages/SettingsPage/index.js +1171 -0
  84. package/src/react/pages/SettingsPage/index.styles.js +122 -0
  85. package/src/react/print/jobs.js +147 -0
  86. package/src/react/print/providers/local.js +53 -0
  87. package/src/react/print/providers/remote.js +81 -0
  88. package/src/react/print/selection.js +296 -0
  89. package/src/react/print/usePrintButtonController.js +474 -0
  90. package/src/react/router/routes.js +42 -0
  91. package/src/react/services/NetworkPrinterService.native.js +391 -0
  92. package/src/react/services/NetworkPrinterService.web.js +15 -0
  93. package/src/react/stores/index.js +128 -0
  94. package/src/react/stores/storeErrorBridge.js +36 -0
  95. package/src/react/utils/backgroundRuntimeRegistration.js +69 -0
  96. package/src/react/utils/cashPayment.js +87 -0
  97. package/src/react/utils/commercialDocumentOrders.js +269 -0
  98. package/src/react/utils/dateRangeFilter.js +264 -0
  99. package/src/react/utils/deviceRuntime.js +193 -0
  100. package/src/react/utils/entityDisplay.js +231 -0
  101. package/src/react/utils/entityLog.js +649 -0
  102. package/src/react/utils/fileUrl.js +102 -0
  103. package/src/react/utils/frontendDebugLog.js +45 -0
  104. package/src/react/utils/googleMapsConfig.js +49 -0
  105. package/src/react/utils/importStatus.js +100 -0
  106. package/src/react/utils/invoiceDueDateMessages.js +14 -0
  107. package/src/react/utils/invoicePresentation.js +46 -0
  108. package/src/react/utils/logSettings.js +143 -0
  109. package/src/react/utils/maintenanceSettings.js +53 -0
  110. package/src/react/utils/managerOrderNotifications.js +523 -0
  111. package/src/react/utils/mapNavigation.js +76 -0
  112. package/src/react/utils/menuCatalogConfig.js +8 -0
  113. package/src/react/utils/menuCatalogDownload.js +157 -0
  114. package/src/react/utils/networkCameraDevices.js +175 -0
  115. package/src/react/utils/networkDeviceProfiles.js +135 -0
  116. package/src/react/utils/normalizedCatalogDownload.js +157 -0
  117. package/src/react/utils/notificationNavigation.js +94 -0
  118. package/src/react/utils/notificationSound.js +42 -0
  119. package/src/react/utils/paymentDevices.js +397 -0
  120. package/src/react/utils/paymentGatewayExecution.js +91 -0
  121. package/src/react/utils/paymentOptions.js +88 -0
  122. package/src/react/utils/peopleDisplay.js +39 -0
  123. package/src/react/utils/printerDevices.js +465 -0
  124. package/src/react/utils/remotePayment.js +61 -0
  125. package/src/react/utils/runtimeFooter.js +475 -0
  126. package/src/react/utils/runtimeLanguage.js +50 -0
  127. package/src/react/utils/runtimeMenu.js +224 -0
  128. package/src/react/utils/screenMetrics.js +30 -0
  129. package/src/react/utils/shopConfig.js +294 -0
  130. package/src/react/utils/shopFranchises.js +114 -0
  131. package/src/react/utils/socketRuntimePipeline.js +454 -0
  132. package/src/react/utils/spoolAckTracker.js +77 -0
  133. package/src/react/utils/storeColumns.js +427 -0
  134. package/src/react/utils/systemErrorChannel.js +109 -0
  135. package/src/react/utils/systemErrorMessage.js +67 -0
  136. package/src/react/utils/toastPresentation.js +53 -0
  137. package/src/react/utils/websocketSharedRuntime.js +826 -0
  138. package/src/store/acl/getters.js +4 -0
  139. package/src/store/acl/index.js +19 -0
  140. package/src/store/acl/mutation_types.js +4 -0
  141. package/src/store/acl/mutations.js +17 -0
  142. package/src/store/address/city.js +22 -0
  143. package/src/store/categories/category_file/index.js +25 -0
  144. package/src/store/categories/index.js +93 -0
  145. package/src/store/configs/customActions.js +131 -0
  146. package/src/store/configs/index.js +25 -0
  147. package/src/store/connections/customActions.js +26 -0
  148. package/src/store/connections/index.js +124 -0
  149. package/src/store/device/index.js +22 -0
  150. package/src/store/device_config/customActions.js +190 -0
  151. package/src/store/device_config/index.js +28 -0
  152. package/src/store/entity_log/customActions.js +63 -0
  153. package/src/store/entity_log/index.js +25 -0
  154. package/src/store/extra/data.js +85 -0
  155. package/src/store/extra/fields.js +88 -0
  156. package/src/store/file/actions.js +27 -0
  157. package/src/store/file/index.js +24 -0
  158. package/src/store/imports/index.js +88 -0
  159. package/src/store/print/customActions.js +251 -0
  160. package/src/store/print/index.js +28 -0
  161. package/src/store/printer/customActions.js +54 -0
  162. package/src/store/printer/index.js +26 -0
  163. package/src/store/runtime_debug/customActions.js +71 -0
  164. package/src/store/runtime_debug/index.js +30 -0
  165. package/src/store/status/index.js +65 -0
  166. package/src/store/timezones/index.js +43 -0
  167. package/src/store/websocket/customActions.js +24 -0
  168. package/src/store/websocket/index.js +28 -0
  169. package/src/tests/react/components/LauncherModeBridge.test.js +93 -0
  170. package/src/tests/react/config/deviceConfigBootstrap.test.js +337 -0
  171. package/src/tests/react/print/localPrintProvider.test.js +45 -0
  172. package/src/tests/react/utils/backgroundRuntimeRegistration.test.js +123 -0
  173. package/src/tests/react/utils/cashPayment.test.js +41 -0
  174. package/src/tests/react/utils/dateRangeFilter.test.mjs +29 -0
  175. package/src/tests/react/utils/entityDisplay.test.js +18 -0
  176. package/src/tests/react/utils/googleMapsConfig.test.js +32 -0
  177. package/src/tests/react/utils/importStatus.test.js +85 -0
  178. package/src/tests/react/utils/invoicePresentation.test.mjs +40 -0
  179. package/src/tests/react/utils/managerOrderNotifications.test.js +127 -0
  180. package/src/tests/react/utils/mapNavigation.test.js +58 -0
  181. package/src/tests/react/utils/notificationNavigation.test.js +39 -0
  182. package/src/tests/react/utils/notificationSound.test.js +27 -0
  183. package/src/tests/react/utils/paymentDevices.test.js +88 -0
  184. package/src/tests/react/utils/runtimeFooter.test.js +429 -0
  185. package/src/tests/react/utils/runtimeLanguage.test.js +80 -0
  186. package/src/tests/react/utils/runtimeMenu.test.js +174 -0
  187. package/src/tests/react/utils/shopFranchises.test.js +82 -0
  188. package/src/tests/react/utils/socketRuntimePipeline.test.js +124 -0
  189. package/src/tests/react/utils/spoolAckTracker.test.js +40 -0
  190. package/src/tests/react/utils/storeErrorBridge.test.js +66 -0
  191. package/src/tests/react/utils/systemErrorChannel.test.js +58 -0
  192. package/src/tests/react/utils/systemErrorMessage.test.js +59 -0
  193. package/src/tests/react/utils/toastPresentation.test.js +75 -0
  194. package/src/tests/react/utils/translate.test.mjs +396 -0
  195. package/src/tests/react/utils/websocketSharedRuntime.test.js +245 -0
  196. package/src/utils/apiEntryPoint.js +31 -0
  197. package/src/utils/appDomain.js +50 -0
  198. package/src/utils/config.js +14 -0
  199. package/src/utils/formatter.js +419 -0
  200. package/src/utils/integrationConfigs.js +79 -0
  201. package/src/utils/methods.js +23 -0
  202. package/src/utils/oauth.js +42 -0
  203. package/src/utils/translate.js +620 -0
@@ -0,0 +1,778 @@
1
+ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
2
+ import {api} from '@controleonline/ui-common/src/api';
3
+ import {
4
+ decodeNetworkPrinterPayload,
5
+ printOnNetworkPrinter,
6
+ } from '@controleonline/ui-common/src/react/services/NetworkPrinterService';
7
+ import {
8
+ DEFAULT_NETWORK_PRINTER_PORT,
9
+ DEFAULT_NETWORK_PRINTER_CODE_PAGE,
10
+ DISPLAY_DEVICE_TYPE,
11
+ PDV_DEVICE_TYPE,
12
+ getManagedPrinterDevices,
13
+ getPrinterHost,
14
+ NETWORK_PRINTER_CODE_PAGE_CONFIG_KEY,
15
+ NETWORK_PRINTER_PORT_CONFIG_KEY,
16
+ normalizePrinterPort,
17
+ } from '@controleonline/ui-common/src/react/utils/printerDevices';
18
+ import {isWebRuntimeDevice} from '@controleonline/ui-common/src/react/utils/deviceRuntime';
19
+ import {
20
+ isLocalCieloPrintCapableDeviceConfig,
21
+ normalizeDeviceId,
22
+ } from '@controleonline/ui-common/src/react/utils/paymentDevices';
23
+ import {
24
+ forgetTrackedSpool,
25
+ hasTrackedSpool,
26
+ pruneTrackedSpools,
27
+ rememberTrackedSpool,
28
+ } from '@controleonline/ui-common/src/react/utils/spoolAckTracker';
29
+ import {PRINT_JOB_TYPE_SPOOL} from '@controleonline/ui-common/src/react/print/jobs';
30
+ import {printOnLocalCielo} from '@controleonline/ui-common/src/react/print/providers/local';
31
+ import {executeRemotePrintRequest} from '@controleonline/ui-common/src/react/print/providers/remote';
32
+ import {useStore} from '@store';
33
+
34
+ const SOCKET_PRINT_POLL_INTERVAL_DISCONNECTED = 10000;
35
+ const SOCKET_PRINT_POLL_DELAY_CONNECTED = 60000;
36
+ const SPOOL_ACK_RETRY_DELAY_MS = 10000;
37
+
38
+ const normalizeDeviceType = value => String(value || '').trim().toUpperCase();
39
+
40
+ const extractCollectionMembers = data => {
41
+ if (Array.isArray(data)) {
42
+ return data;
43
+ }
44
+
45
+ if (Array.isArray(data?.member)) {
46
+ return data.member;
47
+ }
48
+
49
+ if (Array.isArray(data?.['hydra:member'])) {
50
+ return data['hydra:member'];
51
+ }
52
+
53
+ return [];
54
+ };
55
+
56
+ const PrintService = () => {
57
+ const peopleStore = useStore('people');
58
+ const peopleGetters = peopleStore.getters;
59
+ const printStore = useStore('print');
60
+ const printGetters = printStore.getters;
61
+ const printActions = printStore.actions;
62
+ const deviceStore = useStore('device');
63
+ const deviceGetters = deviceStore.getters;
64
+ const deviceConfigStore = useStore('device_config');
65
+ const deviceConfigGetters = deviceConfigStore.getters;
66
+ const websocketStore = useStore('websocket');
67
+ const websocketGetters = websocketStore.getters;
68
+
69
+ const {item: storagedDevice} = deviceGetters;
70
+ const {reload, print, items: spool, message, messages} = printGetters;
71
+ const {currentCompany} = peopleGetters;
72
+ const {item: runtimeDeviceConfig, items: companyDeviceConfigs = []} = deviceConfigGetters;
73
+ const {summary: websocketSummary} = websocketGetters;
74
+
75
+ const isPrintingRef = useRef(false);
76
+ const spoolRef = useRef([]);
77
+ // Once the device prints locally, only the backend ack may remain pending.
78
+ const pendingAckSpoolsRef = useRef(new Map());
79
+ const connectedPollTimeoutRef = useRef(null);
80
+ const ackRetryTimeoutRef = useRef(null);
81
+ const [lastPrintCommandAt, setLastPrintCommandAt] = useState(null);
82
+
83
+ const markPrintCommand = useCallback(() => {
84
+ setLastPrintCommandAt(Date.now());
85
+ }, []);
86
+
87
+ useEffect(() => {
88
+ spoolRef.current = Array.isArray(spool) ? spool : [];
89
+ }, [spool]);
90
+
91
+ const resolveSpoolId = useCallback(value => {
92
+ if (!value) return null;
93
+ if (typeof value === 'number') return value;
94
+ if (typeof value === 'string') {
95
+ const numericValue = value.replace(/\D/g, '');
96
+ return numericValue ? Number(numericValue) : null;
97
+ }
98
+
99
+ return (
100
+ resolveSpoolId(value?.id) ||
101
+ resolveSpoolId(value?.spoolId) ||
102
+ resolveSpoolId(value?.spool) ||
103
+ resolveSpoolId(value?.['@id'])
104
+ );
105
+ }, []);
106
+
107
+ const resolveTargetDevice = useCallback(
108
+ printJob =>
109
+ normalizeDeviceId(
110
+ printJob?.device?.device || printJob?.device || storagedDevice?.id,
111
+ ),
112
+ [storagedDevice?.id],
113
+ );
114
+
115
+ const clearAckRetry = useCallback(() => {
116
+ if (ackRetryTimeoutRef.current) {
117
+ clearTimeout(ackRetryTimeoutRef.current);
118
+ ackRetryTimeoutRef.current = null;
119
+ }
120
+ }, []);
121
+
122
+ const scheduleAckRetry = useCallback(() => {
123
+ clearAckRetry();
124
+ ackRetryTimeoutRef.current = setTimeout(() => {
125
+ printActions.setReload(true);
126
+ }, SPOOL_ACK_RETRY_DELAY_MS);
127
+ }, [clearAckRetry, printActions]);
128
+
129
+ const hasPendingSpoolAck = useCallback(
130
+ spoolId => hasTrackedSpool(pendingAckSpoolsRef.current, spoolId),
131
+ [],
132
+ );
133
+
134
+ const rememberPendingSpoolAck = useCallback(
135
+ spoolId => {
136
+ rememberTrackedSpool(pendingAckSpoolsRef.current, spoolId, {
137
+ printedAt: Date.now(),
138
+ });
139
+ },
140
+ [],
141
+ );
142
+
143
+ const forgetPendingSpoolAck = useCallback(
144
+ spoolId => {
145
+ forgetTrackedSpool(pendingAckSpoolsRef.current, spoolId);
146
+ if (pendingAckSpoolsRef.current.size === 0) {
147
+ clearAckRetry();
148
+ }
149
+ },
150
+ [clearAckRetry],
151
+ );
152
+
153
+ const syncPendingSpoolAcks = useCallback(
154
+ openSpools => {
155
+ const openSpoolIds = (Array.isArray(openSpools) ? openSpools : []).map(
156
+ spoolItem => resolveSpoolId(spoolItem),
157
+ );
158
+ pruneTrackedSpools(pendingAckSpoolsRef.current, openSpoolIds);
159
+ if (pendingAckSpoolsRef.current.size === 0) {
160
+ clearAckRetry();
161
+ }
162
+ },
163
+ [clearAckRetry, resolveSpoolId],
164
+ );
165
+
166
+ const finalizeSpoolAck = useCallback(
167
+ async spoolId => {
168
+ if (!spoolId) {
169
+ return false;
170
+ }
171
+
172
+ await printActions.makePrintDone(spoolId);
173
+ forgetPendingSpoolAck(spoolId);
174
+ return true;
175
+ },
176
+ [forgetPendingSpoolAck, printActions],
177
+ );
178
+
179
+ const runtimeDeviceType = useMemo(
180
+ () =>
181
+ normalizeDeviceType(
182
+ runtimeDeviceConfig?.type ||
183
+ runtimeDeviceConfig?.device?.type ||
184
+ storagedDevice?.type,
185
+ ),
186
+ [runtimeDeviceConfig?.device?.type, runtimeDeviceConfig?.type, storagedDevice?.type],
187
+ );
188
+
189
+ const canUseLocalCieloPrint = useMemo(
190
+ () => isLocalCieloPrintCapableDeviceConfig(runtimeDeviceConfig),
191
+ [runtimeDeviceConfig],
192
+ );
193
+
194
+ const managedPrinters = useMemo(
195
+ () =>
196
+ getManagedPrinterDevices({
197
+ deviceConfigs: companyDeviceConfigs,
198
+ companyId: currentCompany?.id,
199
+ managerDeviceId: storagedDevice?.id,
200
+ }),
201
+ [companyDeviceConfigs, currentCompany?.id, storagedDevice?.id],
202
+ );
203
+
204
+ const managedPrinterDeviceIds = useMemo(
205
+ () =>
206
+ Array.from(
207
+ new Set(
208
+ managedPrinters
209
+ .map(printer => normalizeDeviceId(printer?.device))
210
+ .filter(Boolean),
211
+ ),
212
+ ),
213
+ [managedPrinters],
214
+ );
215
+
216
+ const isWebDevice = useMemo(
217
+ () => isWebRuntimeDevice(storagedDevice),
218
+ [storagedDevice],
219
+ );
220
+
221
+ const spoolDeviceIds = useMemo(() => {
222
+ if (!storagedDevice?.id) {
223
+ return [];
224
+ }
225
+
226
+ if (isWebDevice) {
227
+ return [];
228
+ }
229
+
230
+ if (runtimeDeviceType === PDV_DEVICE_TYPE) {
231
+ const deviceId = normalizeDeviceId(storagedDevice.id);
232
+ return Array.from(
233
+ new Set(
234
+ [
235
+ ...(canUseLocalCieloPrint ? [deviceId] : []),
236
+ ...managedPrinterDeviceIds,
237
+ ].filter(Boolean),
238
+ ),
239
+ );
240
+ }
241
+
242
+ if (runtimeDeviceType === DISPLAY_DEVICE_TYPE) {
243
+ return managedPrinterDeviceIds;
244
+ }
245
+
246
+ return [];
247
+ }, [
248
+ isWebDevice,
249
+ canUseLocalCieloPrint,
250
+ managedPrinterDeviceIds,
251
+ runtimeDeviceType,
252
+ storagedDevice?.id,
253
+ ]);
254
+
255
+ const shouldHandleSpool = useMemo(
256
+ () => spoolDeviceIds.length > 0,
257
+ [spoolDeviceIds],
258
+ );
259
+
260
+ const resolveManagedPrinter = useCallback(
261
+ printJob => {
262
+ const targetDeviceId = resolveTargetDevice(printJob);
263
+ return (
264
+ managedPrinters.find(
265
+ printer => normalizeDeviceId(printer?.device) === targetDeviceId,
266
+ ) || null
267
+ );
268
+ },
269
+ [managedPrinters, resolveTargetDevice],
270
+ );
271
+
272
+ useEffect(() => {
273
+ if (!currentCompany?.id) {
274
+ return;
275
+ }
276
+
277
+ printActions
278
+ .ensurePrintDependenciesLoaded({
279
+ companyId: currentCompany.id,
280
+ })
281
+ .catch(() => {});
282
+ }, [currentCompany?.id, printActions]);
283
+
284
+ const loadOpenSpools = useCallback(async () => {
285
+ if (!shouldHandleSpool) {
286
+ pendingAckSpoolsRef.current.clear();
287
+ clearAckRetry();
288
+ printActions.setItems([]);
289
+ printActions.setReload(false);
290
+ return [];
291
+ }
292
+
293
+ const deviceIds = Array.from(new Set(spoolDeviceIds.filter(Boolean)));
294
+
295
+ if (deviceIds.length === 0) {
296
+ pendingAckSpoolsRef.current.clear();
297
+ clearAckRetry();
298
+ printActions.setItems([]);
299
+ printActions.setReload(false);
300
+ return [];
301
+ }
302
+
303
+ try {
304
+ const spoolCollections = await Promise.all(
305
+ deviceIds.map(deviceId =>
306
+ api
307
+ .fetch('spools', {
308
+ params: {
309
+ 'device.device': deviceId,
310
+ 'status.realStatus': 'open',
311
+ },
312
+ })
313
+ .then(extractCollectionMembers)
314
+ .catch(() => []),
315
+ ),
316
+ );
317
+
318
+ const mergedSpools = [];
319
+ const spoolIds = new Set();
320
+
321
+ spoolCollections.flat().forEach(item => {
322
+ const spoolId = resolveSpoolId(item);
323
+ const spoolKey = spoolId ? String(spoolId) : JSON.stringify(item);
324
+
325
+ if (!spoolIds.has(spoolKey)) {
326
+ spoolIds.add(spoolKey);
327
+ mergedSpools.push(item);
328
+ }
329
+ });
330
+
331
+ mergedSpools.sort((left, right) => {
332
+ const leftId = resolveSpoolId(left) || 0;
333
+ const rightId = resolveSpoolId(right) || 0;
334
+ return leftId - rightId;
335
+ });
336
+
337
+ syncPendingSpoolAcks(mergedSpools);
338
+ printActions.setItems(mergedSpools);
339
+ return mergedSpools;
340
+ } finally {
341
+ printActions.setReload(false);
342
+ }
343
+ }, [
344
+ clearAckRetry,
345
+ printActions,
346
+ resolveSpoolId,
347
+ shouldHandleSpool,
348
+ spoolDeviceIds,
349
+ syncPendingSpoolAcks,
350
+ ]);
351
+
352
+ const removeSpoolFromQueue = useCallback(
353
+ spoolId => {
354
+ if (!spoolId) {
355
+ const nextQueue = [...(spoolRef.current || [])];
356
+ nextQueue.shift();
357
+ spoolRef.current = nextQueue;
358
+ printActions.setItems(nextQueue);
359
+ return;
360
+ }
361
+
362
+ const nextQueue = (spoolRef.current || []).filter(
363
+ item => resolveSpoolId(item) !== spoolId,
364
+ );
365
+ spoolRef.current = nextQueue;
366
+ printActions.setItems(nextQueue);
367
+ },
368
+ [printActions, resolveSpoolId],
369
+ );
370
+
371
+ const getSpoolData = useCallback(
372
+ async printJob => {
373
+ const spoolId = resolveSpoolId(printJob);
374
+ if (!spoolId) {
375
+ return printJob || null;
376
+ }
377
+
378
+ try {
379
+ return await printActions.get(spoolId);
380
+ } catch (e) {
381
+ return printJob || null;
382
+ }
383
+ },
384
+ [printActions, resolveSpoolId],
385
+ );
386
+
387
+ const printManagedSpool = useCallback(
388
+ async (printJob, spoolData) => {
389
+ const managedPrinter = resolveManagedPrinter(spoolData || printJob);
390
+ if (!managedPrinter) {
391
+ return false;
392
+ }
393
+
394
+ const printerHost = getPrinterHost(managedPrinter);
395
+ const printerPort = normalizePrinterPort(
396
+ managedPrinter?.configs?.[NETWORK_PRINTER_PORT_CONFIG_KEY] ||
397
+ DEFAULT_NETWORK_PRINTER_PORT,
398
+ );
399
+ const printerCodePage =
400
+ managedPrinter?.configs?.[NETWORK_PRINTER_CODE_PAGE_CONFIG_KEY] ||
401
+ DEFAULT_NETWORK_PRINTER_CODE_PAGE;
402
+
403
+ await printOnNetworkPrinter({
404
+ host: printerHost,
405
+ port: printerPort,
406
+ codePage: printerCodePage,
407
+ payload: decodeNetworkPrinterPayload(spoolData?.file?.content, {
408
+ codePage: printerCodePage,
409
+ }),
410
+ });
411
+
412
+ return true;
413
+ },
414
+ [resolveManagedPrinter],
415
+ );
416
+
417
+ const processResolvedSpool = useCallback(
418
+ async ({printJob, spoolData, removeFromQueue = false}) => {
419
+ const spoolId = resolveSpoolId(spoolData || printJob);
420
+ if (spoolId && hasPendingSpoolAck(spoolId)) {
421
+ try {
422
+ await finalizeSpoolAck(spoolId);
423
+ if (removeFromQueue) {
424
+ removeSpoolFromQueue(spoolId);
425
+ }
426
+ return {spoolId, printed: true, ackPending: false};
427
+ } catch (ackError) {
428
+ scheduleAckRetry();
429
+ return {
430
+ spoolId,
431
+ printed: true,
432
+ ackPending: true,
433
+ error: ackError?.message || '',
434
+ };
435
+ }
436
+ }
437
+
438
+ if (!spoolData?.file?.content) {
439
+ if (removeFromQueue) {
440
+ removeSpoolFromQueue(spoolId);
441
+ }
442
+ return {spoolId, printed: false};
443
+ }
444
+
445
+ const managedPrinterPrinted = await printManagedSpool(printJob, spoolData);
446
+
447
+ if (!managedPrinterPrinted) {
448
+ if (!canUseLocalCieloPrint) {
449
+ throw new Error(
450
+ global.t?.t('orders', 'message', 'localPrintUnavailable') ||
451
+ 'Este equipamento nao pode imprimir localmente. Configure uma impressora de rede.',
452
+ );
453
+ }
454
+
455
+ await printOnLocalCielo(spoolData.file.content);
456
+ }
457
+
458
+ if (spoolId) {
459
+ rememberPendingSpoolAck(spoolId);
460
+ try {
461
+ await finalizeSpoolAck(spoolId);
462
+ } catch (ackError) {
463
+ scheduleAckRetry();
464
+ return {
465
+ spoolId,
466
+ printed: true,
467
+ ackPending: true,
468
+ error: ackError?.message || '',
469
+ };
470
+ }
471
+ }
472
+
473
+ if (removeFromQueue) {
474
+ removeSpoolFromQueue(spoolId);
475
+ }
476
+
477
+ return {spoolId, printed: true};
478
+ },
479
+ [
480
+ finalizeSpoolAck,
481
+ hasPendingSpoolAck,
482
+ canUseLocalCieloPrint,
483
+ printManagedSpool,
484
+ removeSpoolFromQueue,
485
+ rememberPendingSpoolAck,
486
+ resolveSpoolId,
487
+ scheduleAckRetry,
488
+ ],
489
+ );
490
+
491
+ const resolveRequestedTargetDevice = useCallback(
492
+ printJob =>
493
+ normalizeDeviceId(
494
+ printJob?.targetPrinterDevice ||
495
+ printJob?.device?.device ||
496
+ printJob?.device ||
497
+ storagedDevice?.id,
498
+ ),
499
+ [storagedDevice?.id],
500
+ );
501
+
502
+ const resolveRequestedTargetDeviceType = useCallback(
503
+ printJob =>
504
+ normalizeDeviceType(
505
+ printJob?.targetPrinterType ||
506
+ printJob?.deviceType ||
507
+ printJob?.type ||
508
+ storagedDevice?.type,
509
+ ),
510
+ [storagedDevice?.type],
511
+ );
512
+
513
+ const goPrint = useCallback(
514
+ async printJob => {
515
+ if (isPrintingRef.current) {
516
+ return;
517
+ }
518
+
519
+ const spoolId = resolveSpoolId(printJob);
520
+ if (!spoolId) {
521
+ removeSpoolFromQueue(null);
522
+ return;
523
+ }
524
+
525
+ isPrintingRef.current = true;
526
+
527
+ try {
528
+ const data = await getSpoolData(printJob);
529
+ await processResolvedSpool({
530
+ printJob,
531
+ spoolData: data,
532
+ removeFromQueue: true,
533
+ });
534
+ } catch (e) {
535
+ printActions.setError(
536
+ e?.message || global.t?.t('orders', 'message', 'printProcessingError'),
537
+ );
538
+ removeSpoolFromQueue(spoolId);
539
+ } finally {
540
+ isPrintingRef.current = false;
541
+ }
542
+ },
543
+ [
544
+ getSpoolData,
545
+ printActions,
546
+ processResolvedSpool,
547
+ removeSpoolFromQueue,
548
+ resolveSpoolId,
549
+ ],
550
+ );
551
+
552
+ const executePrintRequest = useCallback(
553
+ async printJob => {
554
+ const requestKey = String(printJob?.requestKey || '').trim();
555
+ const normalizedPrintType = String(
556
+ printJob?.type || printJob?.printType || '',
557
+ )
558
+ .trim()
559
+ .toLowerCase();
560
+
561
+ if (requestKey) {
562
+ printActions.setActiveRequestKey(requestKey);
563
+ }
564
+
565
+ printActions.setLastCompletedRequest(null);
566
+
567
+ try {
568
+ if (normalizedPrintType === PRINT_JOB_TYPE_SPOOL) {
569
+ const spoolData = await getSpoolData({
570
+ spoolId: printJob?.spoolId,
571
+ id: printJob?.spoolId,
572
+ });
573
+ const result = await processResolvedSpool({
574
+ printJob,
575
+ spoolData,
576
+ removeFromQueue: false,
577
+ });
578
+
579
+ printActions.setLastCompletedRequest({
580
+ requestKey,
581
+ status: 'success',
582
+ completedAt: Date.now(),
583
+ ackPending: result?.ackPending === true,
584
+ spoolId: result?.spoolId || null,
585
+ targetDeviceId: normalizeDeviceId(spoolData?.device?.device),
586
+ });
587
+ return;
588
+ }
589
+
590
+ const targetDeviceId = resolveRequestedTargetDevice(printJob);
591
+ const targetDeviceType = resolveRequestedTargetDeviceType(printJob);
592
+
593
+ if (!targetDeviceId) {
594
+ throw new Error(
595
+ global.t?.t('orders', 'title', 'selectPrinter') ||
596
+ 'Selecione a impressora.',
597
+ );
598
+ }
599
+
600
+ await executeRemotePrintRequest({
601
+ printActions,
602
+ printJob: {
603
+ ...printJob,
604
+ type: normalizedPrintType,
605
+ },
606
+ targetDeviceId,
607
+ targetDeviceType,
608
+ currentCompanyId: printJob?.companyId || currentCompany?.id,
609
+ });
610
+
611
+ if (
612
+ shouldHandleSpool &&
613
+ spoolDeviceIds.includes(normalizeDeviceId(targetDeviceId))
614
+ ) {
615
+ markPrintCommand();
616
+ printActions.setReload(true);
617
+ }
618
+
619
+ printActions.setLastCompletedRequest({
620
+ requestKey,
621
+ status: 'success',
622
+ completedAt: Date.now(),
623
+ targetDeviceId,
624
+ });
625
+ } catch (e) {
626
+ const errorMessage =
627
+ e?.message || global.t?.t('orders', 'message', 'printProcessingError');
628
+ printActions.setError(errorMessage);
629
+ printActions.setLastCompletedRequest({
630
+ requestKey,
631
+ status: 'error',
632
+ completedAt: Date.now(),
633
+ error: errorMessage,
634
+ });
635
+ } finally {
636
+ if (requestKey) {
637
+ printActions.setActiveRequestKey('');
638
+ }
639
+ }
640
+ },
641
+ [
642
+ currentCompany?.id,
643
+ getSpoolData,
644
+ markPrintCommand,
645
+ printActions,
646
+ processResolvedSpool,
647
+ resolveRequestedTargetDevice,
648
+ resolveRequestedTargetDeviceType,
649
+ shouldHandleSpool,
650
+ spoolDeviceIds,
651
+ ],
652
+ );
653
+
654
+ useEffect(() => {
655
+ if (shouldHandleSpool) {
656
+ printActions.setReload(true);
657
+ return;
658
+ }
659
+
660
+ printActions.setItems([]);
661
+ printActions.setReload(false);
662
+ }, [printActions, shouldHandleSpool]);
663
+
664
+ useEffect(() => {
665
+ if (!reload) {
666
+ return;
667
+ }
668
+
669
+ loadOpenSpools();
670
+ }, [loadOpenSpools, reload]);
671
+
672
+ useEffect(() => {
673
+ if (!shouldHandleSpool || websocketSummary?.connected === true) {
674
+ return;
675
+ }
676
+
677
+ printActions.setReload(true);
678
+ const intervalId = setInterval(() => {
679
+ printActions.setReload(true);
680
+ }, SOCKET_PRINT_POLL_INTERVAL_DISCONNECTED);
681
+
682
+ return () => {
683
+ clearInterval(intervalId);
684
+ };
685
+ }, [printActions, shouldHandleSpool, websocketSummary?.connected]);
686
+
687
+ useEffect(() => {
688
+ if (connectedPollTimeoutRef.current) {
689
+ clearTimeout(connectedPollTimeoutRef.current);
690
+ connectedPollTimeoutRef.current = null;
691
+ }
692
+
693
+ if (!shouldHandleSpool || !websocketSummary?.connected || !lastPrintCommandAt) {
694
+ return;
695
+ }
696
+
697
+ const elapsed = Date.now() - lastPrintCommandAt;
698
+ const delay = Math.max(SOCKET_PRINT_POLL_DELAY_CONNECTED - elapsed, 0);
699
+
700
+ connectedPollTimeoutRef.current = setTimeout(() => {
701
+ printActions.setReload(true);
702
+ }, delay);
703
+
704
+ return () => {
705
+ if (connectedPollTimeoutRef.current) {
706
+ clearTimeout(connectedPollTimeoutRef.current);
707
+ connectedPollTimeoutRef.current = null;
708
+ }
709
+ };
710
+ }, [
711
+ lastPrintCommandAt,
712
+ printActions,
713
+ shouldHandleSpool,
714
+ websocketSummary?.connected,
715
+ ]);
716
+
717
+ useEffect(() => {
718
+ if (print && print.length > 0) {
719
+ for (const p of print) {
720
+ printActions.addToQueue(() => executePrintRequest(p));
721
+ }
722
+ printActions.initQueue(() => {
723
+ printActions.setPrint([]);
724
+ });
725
+ }
726
+ }, [
727
+ executePrintRequest,
728
+ print,
729
+ printActions,
730
+ ]);
731
+
732
+ useEffect(() => {
733
+ if (!spoolRef.current?.length || isPrintingRef.current) {
734
+ return;
735
+ }
736
+
737
+ goPrint(spoolRef.current[0]);
738
+ }, [goPrint, spool]);
739
+
740
+ useEffect(
741
+ () => () => {
742
+ clearAckRetry();
743
+ },
744
+ [clearAckRetry],
745
+ );
746
+
747
+ useEffect(() => {
748
+ if (!message || Object.keys(message).length === 0) {
749
+ return;
750
+ }
751
+
752
+ if (message?.action === 'print' || message?.store === 'print') {
753
+ if (shouldHandleSpool) {
754
+ markPrintCommand();
755
+ printActions.setReload(true);
756
+ }
757
+ }
758
+
759
+ printActions.setMessage(null);
760
+ }, [markPrintCommand, message, printActions, shouldHandleSpool]);
761
+
762
+ useEffect(() => {
763
+ if (
764
+ messages &&
765
+ messages.length > 0 &&
766
+ (!message || Object.keys(message).length === 0)
767
+ ) {
768
+ const queuedMessages = [...messages];
769
+ printActions.setMessage(queuedMessages.pop());
770
+ printActions.setMessages(queuedMessages);
771
+ }
772
+ }, [messages, message, printActions]);
773
+
774
+ return null;
775
+ };
776
+
777
+ export default PrintService;
778
+ // TODO(store-first): quando este arquivo for mexido, mover a leitura para stores, remover api.fetch e evitar repassar dados em objetos quando o store ja resolver isso.