@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,157 @@
1
+ import {Platform} from 'react-native';
2
+ import {env} from '@env';
3
+ import {File, Paths} from 'expo-file-system';
4
+ import * as Sharing from 'expo-sharing';
5
+
6
+ import {api} from '@controleonline/ui-common/src/api';
7
+ import {resolveAppDomain} from '@controleonline/ui-common/src/utils/appDomain';
8
+ import {
9
+ normalizeModelReference,
10
+ } from '@controleonline/ui-common/src/react/utils/menuCatalogConfig';
11
+
12
+ const normalizeId = value => String(value || '').replace(/\D+/g, '').trim();
13
+
14
+ const slugifyFileName = value => {
15
+ const normalized = String(value || '')
16
+ .normalize('NFD')
17
+ .replace(/[\u0300-\u036f]/g, '')
18
+ .toLowerCase()
19
+ .replace(/[^a-z0-9]+/g, '-')
20
+ .replace(/^-+|-+$/g, '');
21
+
22
+ return normalized || 'catalogo';
23
+ };
24
+
25
+ const extractFilenameFromDisposition = value => {
26
+ const utf8Match = String(value || '').match(/filename\*=UTF-8''([^;]+)/i);
27
+ if (utf8Match?.[1]) {
28
+ return decodeURIComponent(utf8Match[1]).trim();
29
+ }
30
+
31
+ const fallbackMatch = String(value || '').match(/filename="?([^";]+)"?/i);
32
+ return fallbackMatch?.[1]?.trim() || null;
33
+ };
34
+
35
+ const extractDownloadErrorMessage = async response => {
36
+ const fallback = 'Nao foi possivel baixar o cardapio.';
37
+
38
+ try {
39
+ const payload = await response.text();
40
+ if (!payload) {
41
+ return fallback;
42
+ }
43
+
44
+ try {
45
+ const parsed = JSON.parse(payload);
46
+ return parsed?.error || parsed?.description || parsed?.message || fallback;
47
+ } catch (error) {
48
+ return payload;
49
+ }
50
+ } catch (error) {
51
+ return fallback;
52
+ }
53
+ };
54
+
55
+ const buildDownloadUrl = ({apiEntrypoint, companyId, modelReference}) => {
56
+ const searchParams = new URLSearchParams();
57
+ searchParams.set('company', String(companyId));
58
+
59
+ const normalizedModelReference = normalizeModelReference(modelReference);
60
+ if (normalizedModelReference) {
61
+ searchParams.set('model', normalizedModelReference);
62
+ }
63
+
64
+ return `${apiEntrypoint}/products/menu/download?${searchParams.toString()}`;
65
+ };
66
+
67
+ export const downloadMenuCatalog = async ({
68
+ companyId,
69
+ companyName,
70
+ modelReference,
71
+ } = {}) => {
72
+ const normalizedCompanyId = normalizeId(companyId);
73
+ if (!normalizedCompanyId) {
74
+ throw new Error('Empresa nao selecionada.');
75
+ }
76
+
77
+ const apiEntrypoint = String(env.API_ENTRYPOINT || '').replace(/\/$/, '');
78
+ if (!apiEntrypoint) {
79
+ throw new Error('API_ENTRYPOINT nao configurado.');
80
+ }
81
+
82
+ const baseFileName = `cardapio-${slugifyFileName(
83
+ companyName || normalizedCompanyId,
84
+ )}.pdf`;
85
+
86
+ const headers = {
87
+ Accept: 'application/pdf',
88
+ 'App-Domain': resolveAppDomain(env.DOMAIN),
89
+ };
90
+
91
+ const token = await api.getToken();
92
+ if (token) {
93
+ headers['API-TOKEN'] = token;
94
+ }
95
+
96
+ const response = await fetch(
97
+ buildDownloadUrl({
98
+ apiEntrypoint,
99
+ companyId: normalizedCompanyId,
100
+ modelReference,
101
+ }),
102
+ {
103
+ method: 'GET',
104
+ headers,
105
+ },
106
+ );
107
+
108
+ if (!response.ok) {
109
+ throw new Error(await extractDownloadErrorMessage(response));
110
+ }
111
+
112
+ const fileName =
113
+ extractFilenameFromDisposition(response.headers.get('content-disposition')) ||
114
+ baseFileName;
115
+ const blob = await response.blob();
116
+
117
+ if (Platform.OS === 'web') {
118
+ const objectUrl = URL.createObjectURL(blob);
119
+ const anchor = document.createElement('a');
120
+ anchor.href = objectUrl;
121
+ anchor.download = fileName;
122
+ document.body.appendChild(anchor);
123
+ anchor.click();
124
+ anchor.remove();
125
+ window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
126
+
127
+ return {
128
+ fileName,
129
+ shared: false,
130
+ savedUri: null,
131
+ };
132
+ }
133
+
134
+ const file = new File(Paths.cache, fileName);
135
+ file.create({intermediates: true, overwrite: true});
136
+ file.write(new Uint8Array(await blob.arrayBuffer()));
137
+
138
+ if (await Sharing.isAvailableAsync()) {
139
+ await Sharing.shareAsync(file.uri, {
140
+ mimeType: 'application/pdf',
141
+ dialogTitle: 'Compartilhar cardapio',
142
+ });
143
+
144
+ return {
145
+ fileName,
146
+ shared: true,
147
+ savedUri: file.uri,
148
+ };
149
+ }
150
+
151
+ return {
152
+ fileName,
153
+ shared: false,
154
+ savedUri: file.uri,
155
+ };
156
+ };
157
+ // TODO(store-first): quando este arquivo for mexido, mover a leitura para stores e evitar chamadas HTTP diretas quando o store ja resolver isso.
@@ -0,0 +1,175 @@
1
+ import {
2
+ normalizePrinterHost,
3
+ normalizePrinterPort,
4
+ } from '@controleonline/ui-common/src/react/utils/printerDevices';
5
+ import {normalizeDeviceId} from '@controleonline/ui-common/src/react/utils/paymentDevices';
6
+
7
+ export const NETWORK_CAMERA_MANAGER_DEVICE_CONFIG_KEY =
8
+ 'camera-network-manager-device';
9
+ export const NETWORK_CAMERA_PORT_CONFIG_KEY = 'camera-network-port';
10
+ export const NETWORK_CAMERA_PROTOCOL_CONFIG_KEY = 'camera-network-protocol';
11
+ export const NETWORK_CAMERA_STREAM_PATH_CONFIG_KEY =
12
+ 'camera-network-stream-path';
13
+ export const NETWORK_CAMERA_USERNAME_CONFIG_KEY = 'camera-network-username';
14
+ export const NETWORK_CAMERA_PASSWORD_CONFIG_KEY = 'camera-network-password';
15
+
16
+ export const DEFAULT_NETWORK_CAMERA_PORT = '554';
17
+ export const DEFAULT_NETWORK_CAMERA_PROTOCOL = 'rtsp';
18
+ export const DEFAULT_NETWORK_CAMERA_MANUFACTURER = 'Hikvision';
19
+ export const DEFAULT_NETWORK_CAMERA_MODEL = 'DS-2CD1123G0E-I';
20
+ export const NETWORK_CAMERA_PROTOCOL_OPTIONS = [
21
+ {value: 'rtsp', label: 'RTSP'},
22
+ {value: 'onvif', label: 'ONVIF'},
23
+ {value: 'http', label: 'HTTP'},
24
+ {value: 'https', label: 'HTTPS'},
25
+ ];
26
+
27
+ const safeTrim = value => String(value || '').trim();
28
+
29
+ const parseMetadataObject = metadata => {
30
+ if (!metadata) {
31
+ return {};
32
+ }
33
+
34
+ if (typeof metadata === 'string') {
35
+ try {
36
+ const parsed = JSON.parse(metadata);
37
+ return parsed && typeof parsed === 'object' ? parsed : {};
38
+ } catch {
39
+ return {};
40
+ }
41
+ }
42
+
43
+ return typeof metadata === 'object' ? {...metadata} : {};
44
+ };
45
+
46
+ export const normalizeCameraProtocol = value =>
47
+ NETWORK_CAMERA_PROTOCOL_OPTIONS.find(
48
+ option => option.value === safeTrim(value).toLowerCase(),
49
+ )?.value || DEFAULT_NETWORK_CAMERA_PROTOCOL;
50
+
51
+ export const normalizeCameraStreamPath = value => safeTrim(value);
52
+ export const normalizeCameraCredential = value => safeTrim(value);
53
+
54
+ export const getCameraMetadata = metadata => parseMetadataObject(metadata);
55
+
56
+ export const getCameraMetadataField = (metadata, field) => {
57
+ const parsedMetadata = getCameraMetadata(metadata);
58
+ const cameraField = safeTrim(parsedMetadata?.camera?.[field]);
59
+ if (cameraField) {
60
+ return cameraField;
61
+ }
62
+
63
+ if (field === 'manufacturer') {
64
+ return safeTrim(parsedMetadata?.system?.manufacturer);
65
+ }
66
+
67
+ if (field === 'model') {
68
+ return safeTrim(parsedMetadata?.system?.model);
69
+ }
70
+
71
+ if (field === 'version') {
72
+ return safeTrim(parsedMetadata?.system?.version);
73
+ }
74
+
75
+ return '';
76
+ };
77
+
78
+ export const buildNetworkCameraMetadata = ({
79
+ existingMetadata = {},
80
+ host,
81
+ manufacturer,
82
+ model,
83
+ version,
84
+ }) => {
85
+ const parsedMetadata = getCameraMetadata(existingMetadata);
86
+ const normalizedManufacturer =
87
+ safeTrim(manufacturer) ||
88
+ getCameraMetadataField(parsedMetadata, 'manufacturer') ||
89
+ null;
90
+ const normalizedModel =
91
+ safeTrim(model) || getCameraMetadataField(parsedMetadata, 'model') || null;
92
+ const normalizedVersion =
93
+ safeTrim(version) ||
94
+ getCameraMetadataField(parsedMetadata, 'version') ||
95
+ null;
96
+ const normalizedHost =
97
+ normalizePrinterHost(host) || safeTrim(parsedMetadata?.camera?.host) || null;
98
+
99
+ return {
100
+ ...parsedMetadata,
101
+ runtime: safeTrim(parsedMetadata?.runtime) || 'network',
102
+ camera: {
103
+ ...(parsedMetadata?.camera || {}),
104
+ host: normalizedHost,
105
+ manufacturer: normalizedManufacturer,
106
+ model: normalizedModel,
107
+ version: normalizedVersion,
108
+ },
109
+ system: {
110
+ ...(parsedMetadata?.system || {}),
111
+ name: safeTrim(parsedMetadata?.system?.name) || 'network-camera',
112
+ version: normalizedVersion,
113
+ manufacturer: normalizedManufacturer,
114
+ model: normalizedModel,
115
+ hardwareType: 'camera',
116
+ },
117
+ };
118
+ };
119
+
120
+ export const getNetworkCameraConfigValues = configs => ({
121
+ managerDeviceId: normalizeDeviceId(
122
+ configs?.[NETWORK_CAMERA_MANAGER_DEVICE_CONFIG_KEY],
123
+ ),
124
+ port: normalizePrinterPort(
125
+ configs?.[NETWORK_CAMERA_PORT_CONFIG_KEY] || DEFAULT_NETWORK_CAMERA_PORT,
126
+ ),
127
+ protocol: normalizeCameraProtocol(
128
+ configs?.[NETWORK_CAMERA_PROTOCOL_CONFIG_KEY] || DEFAULT_NETWORK_CAMERA_PROTOCOL,
129
+ ),
130
+ streamPath: normalizeCameraStreamPath(
131
+ configs?.[NETWORK_CAMERA_STREAM_PATH_CONFIG_KEY],
132
+ ),
133
+ username: normalizeCameraCredential(
134
+ configs?.[NETWORK_CAMERA_USERNAME_CONFIG_KEY],
135
+ ),
136
+ password: normalizeCameraCredential(
137
+ configs?.[NETWORK_CAMERA_PASSWORD_CONFIG_KEY],
138
+ ),
139
+ });
140
+
141
+ export const buildNetworkCameraConfigs = ({
142
+ managerDeviceId,
143
+ port,
144
+ protocol,
145
+ streamPath,
146
+ username,
147
+ password,
148
+ }) => {
149
+ const normalizedManagerDeviceId = normalizeDeviceId(managerDeviceId);
150
+ const normalizedPort = normalizePrinterPort(port || DEFAULT_NETWORK_CAMERA_PORT);
151
+ const normalizedProtocol = normalizeCameraProtocol(protocol);
152
+ const normalizedStreamPath = normalizeCameraStreamPath(streamPath);
153
+ const normalizedUsername = normalizeCameraCredential(username);
154
+ const normalizedPassword = normalizeCameraCredential(password);
155
+
156
+ const nextConfigs = {
157
+ [NETWORK_CAMERA_MANAGER_DEVICE_CONFIG_KEY]: normalizedManagerDeviceId,
158
+ [NETWORK_CAMERA_PORT_CONFIG_KEY]: normalizedPort,
159
+ [NETWORK_CAMERA_PROTOCOL_CONFIG_KEY]: normalizedProtocol,
160
+ };
161
+
162
+ if (normalizedStreamPath) {
163
+ nextConfigs[NETWORK_CAMERA_STREAM_PATH_CONFIG_KEY] = normalizedStreamPath;
164
+ }
165
+
166
+ if (normalizedUsername) {
167
+ nextConfigs[NETWORK_CAMERA_USERNAME_CONFIG_KEY] = normalizedUsername;
168
+ }
169
+
170
+ if (normalizedPassword) {
171
+ nextConfigs[NETWORK_CAMERA_PASSWORD_CONFIG_KEY] = normalizedPassword;
172
+ }
173
+
174
+ return nextConfigs;
175
+ };
@@ -0,0 +1,135 @@
1
+ import {
2
+ IP_CAMERA_DEVICE_TYPE,
3
+ PRINT_DEVICE_TYPE,
4
+ PRINTER_DEVICE_TYPE,
5
+ normalizeDeviceType,
6
+ } from '@controleonline/ui-common/src/react/utils/printerDevices';
7
+
8
+ const printerProfile = {
9
+ filterKey: 'PRINTERS',
10
+ filterLabel: 'Impressoras',
11
+ itemLabel: 'Impressora',
12
+ icon: 'printer',
13
+ formRouteName: 'PrinterDeviceForm',
14
+ detailRouteName: 'PrinterDeviceDetail',
15
+ formHeaderTitle: 'Nova Impressora de Rede',
16
+ detailHeaderTitle: 'Impressora de Rede',
17
+ createActionLabel: 'Nova impressora de rede',
18
+ createHelperText:
19
+ 'Cadastre impressoras por IP/hostname e vincule o device local que vai gerenciar a impressao na rede.',
20
+ emptyTitle: 'Nenhuma impressora encontrada',
21
+ emptyDescription:
22
+ 'Cadastre uma impressora de rede para visualizar devices deste tipo.',
23
+ heroTitle: 'Nova impressora de rede',
24
+ heroText:
25
+ 'Cadastre a impressora pelo IP/hostname e vincule qual device local vai executar a impressao na rede.',
26
+ aliasPlaceholder: 'Ex.: Impressora Caixa 1',
27
+ detailHeroFallback: 'Impressora de rede',
28
+ detailLoadingText: 'Atualizando dados da impressora...',
29
+ companyMissingMessage:
30
+ 'Selecione uma empresa antes de cadastrar a impressora.',
31
+ hostAlertTitle: 'IP da impressora',
32
+ hostMissingMessage: 'Informe o IP ou hostname da impressora.',
33
+ managerMissingMessage:
34
+ 'Selecione o PDV ou DISPLAY responsável por receber e encaminhar as impressões.',
35
+ registerErrorMessage: 'Não foi possível cadastrar a impressora.',
36
+ registerErrorTitle: 'Erro ao cadastrar impressora',
37
+ managerSectionDescription:
38
+ 'Este PDV ou DISPLAY será o gateway local que acompanha as impressões desta impressora de rede.',
39
+ createButtonLabel: 'Cadastrar impressora',
40
+ statusSectionDescription:
41
+ 'O app tenta abrir uma conexão TCP direta com a impressora IP para indicar se ela está acessível na rede deste device.',
42
+ registrationSectionTitle: 'Cadastro da impressora',
43
+ detailAliasPlaceholder: 'Nome da impressora',
44
+ registrationAlertTitle: 'Cadastro da impressora',
45
+ routingSectionTitle: 'Roteamento de impressão',
46
+ routingSectionDescription:
47
+ 'O backend continua gerando a impressão para o device da impressora. Aqui você define qual PDV ou DISPLAY local fica responsável por consumir essa fila e falar com a impressora na rede.',
48
+ routingAlertTitle: 'Configuração da impressora',
49
+ hostMissingBeforeSaveMessage:
50
+ 'Informe o IP ou hostname da impressora antes de salvar.',
51
+ managerRoutingMessage:
52
+ 'Selecione qual PDV ou DISPLAY executa a impressão desta impressora.',
53
+ saveBeforeRoutingMessage:
54
+ 'Salve primeiro o cadastro da impressora para aplicar o novo IP antes do roteamento.',
55
+ footerDebugDescription:
56
+ 'Controla se esta impressora mostra apenas a bolinha discreta do socket no rodape global ou se abre as linhas detalhadas de debug.',
57
+ removeSectionTitle: 'Remover cadastro de rede',
58
+ removeSectionDescription:
59
+ 'Use esta acao para excluir apenas o cadastro desta impressora na rede. O equipamento responsavel pela comunicacao continua salvo no sistema.',
60
+ removeButtonLabel: 'Remover cadastro de rede',
61
+ removeConfirmTitle: 'Remover impressora da rede',
62
+ removeConfirmMessage:
63
+ 'Deseja remover o cadastro desta impressora na rede? O equipamento responsavel continuara salvo no sistema.',
64
+ removeSuccessMessage: 'Cadastro da impressora na rede removido com sucesso.',
65
+ removeErrorMessage: 'Nao foi possivel remover o cadastro da impressora na rede.',
66
+ };
67
+
68
+ const ipCameraProfile = {
69
+ filterKey: IP_CAMERA_DEVICE_TYPE,
70
+ filterLabel: 'Cameras IP',
71
+ itemLabel: 'Camera IP',
72
+ icon: 'camera',
73
+ formRouteName: 'IpCameraForm',
74
+ detailRouteName: 'IpCameraDetail',
75
+ formHeaderTitle: 'Nova Camera IP',
76
+ detailHeaderTitle: 'Camera IP',
77
+ createActionLabel: 'Nova camera IP',
78
+ createHelperText:
79
+ 'Cadastre cameras IP por IP/hostname e vincule o device local que vai acompanhar a comunicacao na rede.',
80
+ emptyTitle: 'Nenhuma camera IP encontrada',
81
+ emptyDescription:
82
+ 'Cadastre uma camera IP para visualizar devices deste tipo.',
83
+ heroTitle: 'Nova camera IP',
84
+ heroText:
85
+ 'Cadastre a camera pelo IP/hostname e vincule qual device local vai acompanhar a comunicacao na rede.',
86
+ aliasPlaceholder: 'Ex.: Camera Corredor 1',
87
+ detailHeroFallback: 'Camera IP',
88
+ detailLoadingText: 'Atualizando dados da camera IP...',
89
+ companyMissingMessage:
90
+ 'Selecione uma empresa antes de cadastrar a camera IP.',
91
+ hostAlertTitle: 'IP da camera',
92
+ hostMissingMessage: 'Informe o IP ou hostname da camera IP.',
93
+ managerMissingMessage:
94
+ 'Selecione o PDV ou DISPLAY responsavel por acompanhar esta camera IP.',
95
+ registerErrorMessage: 'Nao foi possivel cadastrar a camera IP.',
96
+ registerErrorTitle: 'Erro ao cadastrar camera IP',
97
+ managerSectionDescription:
98
+ 'Este PDV ou DISPLAY sera o gateway local que acompanha esta camera IP na rede.',
99
+ createButtonLabel: 'Cadastrar camera IP',
100
+ statusSectionDescription:
101
+ 'O app tenta abrir uma conexao TCP direta com a camera IP para indicar se ela esta acessivel na rede deste device.',
102
+ registrationSectionTitle: 'Cadastro da camera IP',
103
+ detailAliasPlaceholder: 'Nome da camera IP',
104
+ registrationAlertTitle: 'Cadastro da camera IP',
105
+ routingSectionTitle: 'Acesso da camera',
106
+ routingSectionDescription:
107
+ 'Informe como acessar o stream da camera e qual PDV ou DISPLAY local fica responsavel por acompanhar essa conexao.',
108
+ routingAlertTitle: 'Acesso da camera IP',
109
+ hostMissingBeforeSaveMessage:
110
+ 'Informe o IP ou hostname da camera IP antes de salvar.',
111
+ managerRoutingMessage:
112
+ 'Selecione qual PDV ou DISPLAY acompanha esta camera IP.',
113
+ saveBeforeRoutingMessage:
114
+ 'Salve primeiro o cadastro da camera IP antes de configurar o acesso.',
115
+ footerDebugDescription:
116
+ 'Controla se esta camera IP mostra apenas a bolinha discreta do socket no rodape global ou se abre as linhas detalhadas de debug.',
117
+ removeSectionTitle: 'Remover cadastro de rede',
118
+ removeSectionDescription:
119
+ 'Use esta acao para excluir apenas o cadastro desta camera na rede. O equipamento responsavel pela comunicacao continua salvo no sistema.',
120
+ removeButtonLabel: 'Remover cadastro de rede',
121
+ removeConfirmTitle: 'Remover camera da rede',
122
+ removeConfirmMessage:
123
+ 'Deseja remover o cadastro desta camera na rede? O equipamento responsavel continuara salvo no sistema.',
124
+ removeSuccessMessage: 'Cadastro da camera na rede removido com sucesso.',
125
+ removeErrorMessage: 'Nao foi possivel remover o cadastro da camera na rede.',
126
+ };
127
+
128
+ const profileByType = {
129
+ [PRINT_DEVICE_TYPE]: printerProfile,
130
+ [PRINTER_DEVICE_TYPE]: printerProfile,
131
+ [IP_CAMERA_DEVICE_TYPE]: ipCameraProfile,
132
+ };
133
+
134
+ export const getNetworkDeviceProfile = type =>
135
+ profileByType[normalizeDeviceType(type)] || printerProfile;
@@ -0,0 +1,157 @@
1
+ import {Platform} from 'react-native';
2
+ import {env} from '@env';
3
+ import {File, Paths} from 'expo-file-system';
4
+ import * as Sharing from 'expo-sharing';
5
+
6
+ import {api} from '@controleonline/ui-common/src/api';
7
+ import {resolveAppDomain} from '@controleonline/ui-common/src/utils/appDomain';
8
+
9
+ const normalizeId = value => String(value || '').replace(/\D+/g, '').trim();
10
+
11
+ const slugifyFileName = value => {
12
+ const normalized = String(value || '')
13
+ .normalize('NFD')
14
+ .replace(/[\u0300-\u036f]/g, '')
15
+ .toLowerCase()
16
+ .replace(/[^a-z0-9]+/g, '-')
17
+ .replace(/^-+|-+$/g, '');
18
+
19
+ return normalized || 'catalogo';
20
+ };
21
+
22
+ const extractFilenameFromDisposition = value => {
23
+ const utf8Match = String(value || '').match(/filename\*=UTF-8''([^;]+)/i);
24
+ if (utf8Match?.[1]) {
25
+ return decodeURIComponent(utf8Match[1]).trim();
26
+ }
27
+
28
+ const fallbackMatch = String(value || '').match(/filename="?([^";]+)"?/i);
29
+ return fallbackMatch?.[1]?.trim() || null;
30
+ };
31
+
32
+ const extractDownloadErrorMessage = async response => {
33
+ const fallback = 'Nao foi possivel baixar o CSV normalizado.';
34
+
35
+ try {
36
+ const payload = await response.text();
37
+ if (!payload) {
38
+ return fallback;
39
+ }
40
+
41
+ try {
42
+ const parsed = JSON.parse(payload);
43
+ return parsed?.error || parsed?.description || parsed?.message || fallback;
44
+ } catch (error) {
45
+ return payload;
46
+ }
47
+ } catch (error) {
48
+ return fallback;
49
+ }
50
+ };
51
+
52
+ export const buildNormalizedCatalogFileName = companyName =>
53
+ `catalogo-normalizado-${slugifyFileName(companyName)}.csv`;
54
+
55
+ export const buildNormalizedCatalogDownloadUrl = ({
56
+ apiEntrypoint,
57
+ companyId,
58
+ context,
59
+ }) => {
60
+ const searchParams = new URLSearchParams();
61
+ searchParams.set('company', String(companyId));
62
+ searchParams.set('context', String(context || 'products').trim() || 'products');
63
+
64
+ return `${apiEntrypoint}/products/catalog/download-normalized?${searchParams.toString()}`;
65
+ };
66
+
67
+ export const downloadNormalizedCatalog = async ({
68
+ companyId,
69
+ companyName,
70
+ context,
71
+ } = {}) => {
72
+ const normalizedCompanyId = normalizeId(companyId);
73
+ if (!normalizedCompanyId) {
74
+ throw new Error('Empresa nao selecionada.');
75
+ }
76
+
77
+ const apiEntrypoint = String(env.API_ENTRYPOINT || '').replace(/\/$/, '');
78
+ if (!apiEntrypoint) {
79
+ throw new Error('API_ENTRYPOINT nao configurado.');
80
+ }
81
+
82
+ const baseFileName = buildNormalizedCatalogFileName(
83
+ companyName || normalizedCompanyId,
84
+ );
85
+
86
+ const headers = {
87
+ Accept: 'text/csv',
88
+ 'App-Domain': resolveAppDomain(env.DOMAIN),
89
+ };
90
+
91
+ const token = await api.getToken();
92
+ if (token) {
93
+ headers['API-TOKEN'] = token;
94
+ }
95
+
96
+ const response = await fetch(
97
+ buildNormalizedCatalogDownloadUrl({
98
+ apiEntrypoint,
99
+ companyId: normalizedCompanyId,
100
+ context,
101
+ }),
102
+ {
103
+ method: 'GET',
104
+ headers,
105
+ },
106
+ );
107
+
108
+ if (!response.ok) {
109
+ throw new Error(await extractDownloadErrorMessage(response));
110
+ }
111
+
112
+ const fileName =
113
+ extractFilenameFromDisposition(response.headers.get('content-disposition')) ||
114
+ baseFileName;
115
+ const blob = await response.blob();
116
+
117
+ if (Platform.OS === 'web') {
118
+ const objectUrl = URL.createObjectURL(blob);
119
+ const anchor = document.createElement('a');
120
+ anchor.href = objectUrl;
121
+ anchor.download = fileName;
122
+ document.body.appendChild(anchor);
123
+ anchor.click();
124
+ anchor.remove();
125
+ window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
126
+
127
+ return {
128
+ fileName,
129
+ shared: false,
130
+ savedUri: null,
131
+ };
132
+ }
133
+
134
+ const file = new File(Paths.cache, fileName);
135
+ file.create({intermediates: true, overwrite: true});
136
+ file.write(new Uint8Array(await blob.arrayBuffer()));
137
+
138
+ if (await Sharing.isAvailableAsync()) {
139
+ await Sharing.shareAsync(file.uri, {
140
+ mimeType: 'text/csv',
141
+ dialogTitle: 'Compartilhar catalogo normalizado',
142
+ });
143
+
144
+ return {
145
+ fileName,
146
+ shared: true,
147
+ savedUri: file.uri,
148
+ };
149
+ }
150
+
151
+ return {
152
+ fileName,
153
+ shared: false,
154
+ savedUri: file.uri,
155
+ };
156
+ };
157
+ // TODO(store-first): quando este arquivo for mexido, mover a leitura para stores e evitar chamadas HTTP diretas quando o store ja resolver isso.