@controleonline/ui-common 1.2.71 → 1.2.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +100 -36
  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 +2814 -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
@@ -18,15 +18,18 @@ import RuntimeInfoFooter from '@controleonline/ui-common/src/react/components/Ru
18
18
  import { useStore } from '@store';
19
19
  import { api } from '@controleonline/ui-common/src/api';
20
20
  import {app_type} from '@appType';
21
- import { env as APP_ENV } from '@env';
21
+ import {env as APP_ENV} from '@env';
22
22
  import { isPublicRoute } from '../router/publicRoutes';
23
- const { resolveConfiguredLanguage } = require('../utils/runtimeLanguage');
23
+ const {
24
+ buildTranslationBootstrapKey,
25
+ isTranslationBootstrapReady,
26
+ resolveConfiguredLanguage,
27
+ } = require('../utils/runtimeLanguage');
24
28
  import {
25
29
  applyPaletteToRuntimeColors,
26
30
  applyThemeCssVariables,
27
31
  resolveThemePalette,
28
32
  } from '@controleonline/../../src/styles/branding';
29
- import { resolveAppDomain } from '@controleonline/ui-common/src/utils/appDomain';
30
33
  import { colors as runtimeColors } from '@controleonline/../../src/styles/colors';
31
34
  import {
32
35
  buildProviderManagedDeviceConfigs,
@@ -46,6 +49,9 @@ import {
46
49
  import {
47
50
  normalizeRuntimeMenuResponse,
48
51
  } from '@controleonline/ui-common/src/react/utils/runtimeMenu';
52
+ import {
53
+ buildRuntimeZoomStyle,
54
+ } from '@controleonline/ui-common/src/react/utils/runtimeZoom';
49
55
  import stores from '@stores';
50
56
  import packageJson from '@package';
51
57
  import providerStyles from './DefaultProvider.styles';
@@ -143,8 +149,12 @@ const selectRuntimeDeviceConfig = ({
143
149
  })
144
150
  .sort((left, right) => Number(right?.id || 0) - Number(left?.id || 0))[0] || null;
145
151
  };
146
-
147
- export const DefaultProvider = ({ children, onBootstrapReady }) => {
152
+
153
+ export const DefaultProvider = ({
154
+ children,
155
+ currentRouteName = '',
156
+ onBootstrapReady,
157
+ }) => {
148
158
  const appType = String(app_type || '').toUpperCase();
149
159
  const isShopClientApp = appType === 'SHOP';
150
160
  const themeStore = useStore('theme');
@@ -186,17 +196,20 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
186
196
  const hasCurrentCompany =
187
197
  !!currentCompany && Object.entries(currentCompany).length > 0;
188
198
 
189
- const [translateReady, setTranslateReady] = useState(true);
199
+ const [translateReady, setTranslateReady] = useState(false);
200
+ const [activeTranslateBootstrapKey, setActiveTranslateBootstrapKey] =
201
+ useState('');
190
202
  const [deviceConfigFetched, setDeviceConfigFetched] = useState(false);
191
203
  const [mainConfigsDiscovered, setMainConfigsDiscovered] = useState(false);
192
204
  const [deviceRuntimeConfigSynced, setDeviceRuntimeConfigSynced] =
193
205
  useState(false);
194
- const [currentRouteName, setCurrentRouteName] = useState('');
195
206
  const [, setTranslateVersion] = useState(0);
196
207
  const [baseThemeColors, setBaseThemeColors] = useState({});
197
208
  const [bottomNavigationCount, setBottomNavigationCount] = useState(0);
198
209
  const translateBootstrapKeyRef = useRef('');
199
210
  const lastDeviceConfigPeopleIriRef = useRef('');
211
+ const walletPaymentTypeRequestRef = useRef({key: '', promise: null});
212
+ const walletPaymentTypeLoadedKeyRef = useRef('');
200
213
  const [device, setDevice] = useState(
201
214
  JSON.parse(localStorage.getItem('device') || '{}'),
202
215
  );
@@ -211,12 +224,48 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
211
224
  appType: app_type,
212
225
  deviceInfo: device || {},
213
226
  });
227
+ const runtimeUiScaleStyle = useMemo(
228
+ () => buildRuntimeZoomStyle(APP_ENV?.ZOOM, {isWeb: true}),
229
+ [],
230
+ );
214
231
  const deviceConfigPeopleIri = resolveDeviceConfigPeopleIri({
215
232
  appType,
216
233
  currentCompany,
217
234
  user,
218
235
  });
219
236
  const isPublicRouteActive = isPublicRoute(currentRouteName);
237
+ const currentTranslationConfig = JSON.parse(
238
+ localStorage.getItem('config') || '{}',
239
+ );
240
+ const currentTranslationSession = JSON.parse(
241
+ localStorage.getItem('session') || '{}',
242
+ );
243
+ const configuredTranslationLanguage = resolveConfiguredLanguage({
244
+ currentCompany,
245
+ defaultCompany,
246
+ currentConfig: currentTranslationConfig,
247
+ sessionData: currentTranslationSession,
248
+ });
249
+ const expectedTranslateBootstrapKey = isLogged && hasCurrentCompany
250
+ ? buildTranslationBootstrapKey({
251
+ language: configuredTranslationLanguage,
252
+ currentCompanyId: normalizeEntityId(currentCompany?.id),
253
+ defaultCompanyId: normalizeEntityId(defaultCompany?.id),
254
+ })
255
+ : '';
256
+ const requiresTranslateBootstrap = Boolean(
257
+ isLogged &&
258
+ hasCurrentCompany &&
259
+ currentRouteName &&
260
+ !isPublicRouteActive,
261
+ );
262
+ const isTranslateBootstrapReady = isTranslationBootstrapReady({
263
+ activeKey: activeTranslateBootstrapKey,
264
+ expectedKey: expectedTranslateBootstrapKey,
265
+ required: requiresTranslateBootstrap,
266
+ ready: translateReady,
267
+ translator: global.t,
268
+ });
220
269
 
221
270
  const registerBottomNavigation = useCallback(() => {
222
271
  setBottomNavigationCount(current => current + 1);
@@ -274,30 +323,13 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
274
323
  };
275
324
  }, []);
276
325
 
277
- useEffect(() => {
278
- global.setRuntimeRouteName = routeName => {
279
- const normalizedRouteName = String(routeName || '').trim();
280
-
281
- setCurrentRouteName(previousRouteName =>
282
- previousRouteName === normalizedRouteName
283
- ? previousRouteName
284
- : normalizedRouteName,
285
- );
286
- };
287
-
288
- return () => {
289
- if (global.setRuntimeRouteName) {
290
- delete global.setRuntimeRouteName;
291
- }
292
- };
293
- }, []);
294
-
295
326
  useEffect(() => {
296
327
  if (isLogged && !isPublicRouteActive) {
297
328
  return;
298
329
  }
299
330
 
300
331
  setTranslateReady(true);
332
+ setActiveTranslateBootstrapKey('');
301
333
  translateBootstrapKeyRef.current = '';
302
334
  translateActions.setMessages({});
303
335
  translateActions.setPendingMessages?.({});
@@ -305,7 +337,7 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
305
337
  if (global.t) {
306
338
  delete global.t;
307
339
  }
308
- }, [isLogged, isPublicRouteActive]);
340
+ }, [isLogged, isPublicRouteActive, translateActions]);
309
341
 
310
342
  useEffect(() => {
311
343
  if (
@@ -526,18 +558,45 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
526
558
  : device_config?.configs;
527
559
 
528
560
  if (!currentCompany?.id || !paymentConfigSource) {
561
+ walletPaymentTypeRequestRef.current = {key: '', promise: null};
562
+ walletPaymentTypeLoadedKeyRef.current = '';
529
563
  paymentTypeActions.setItems([]);
530
564
  return;
531
565
  }
532
566
 
567
+ const paymentTypeSignature = resolveDevicePaymentTypeIds(paymentConfigSource)
568
+ .sort()
569
+ .join(',');
570
+ const requestKey = `${currentCompany.id}:${paymentTypeSignature}`;
571
+
572
+ if (walletPaymentTypeLoadedKeyRef.current === requestKey) {
573
+ return;
574
+ }
575
+
576
+ const existingRequestPromise =
577
+ walletPaymentTypeRequestRef.current.key === requestKey &&
578
+ walletPaymentTypeRequestRef.current.promise
579
+ ? walletPaymentTypeRequestRef.current.promise
580
+ : null;
581
+
533
582
  let isMounted = true;
534
583
 
535
- api
536
- .fetch('wallet_payment_types', {
537
- params: {
538
- people: `/people/${currentCompany.id}`,
539
- },
540
- })
584
+ const requestPromise =
585
+ existingRequestPromise ||
586
+ api.fetch('wallet_payment_types', {
587
+ params: {
588
+ people: `/people/${currentCompany.id}`,
589
+ },
590
+ });
591
+
592
+ if (!existingRequestPromise) {
593
+ walletPaymentTypeRequestRef.current = {
594
+ key: requestKey,
595
+ promise: requestPromise,
596
+ };
597
+ }
598
+
599
+ requestPromise
541
600
  .then(response => {
542
601
  if (!isMounted) {
543
602
  return;
@@ -561,11 +620,20 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
561
620
  allowedPaymentTypeIds,
562
621
  ),
563
622
  );
623
+ walletPaymentTypeLoadedKeyRef.current = requestKey;
564
624
  })
565
625
  .catch(() => {
566
626
  if (isMounted) {
567
627
  paymentTypeActions.setItems([]);
568
628
  }
629
+ })
630
+ .finally(() => {
631
+ if (
632
+ walletPaymentTypeRequestRef.current.key === requestKey &&
633
+ walletPaymentTypeRequestRef.current.promise === requestPromise
634
+ ) {
635
+ walletPaymentTypeRequestRef.current = {key: '', promise: null};
636
+ }
569
637
  });
570
638
 
571
639
  return () => {
@@ -713,56 +781,48 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
713
781
  }, [currentCompany, isLogged, mainConfigsDiscovered]);
714
782
 
715
783
  useEffect(() => {
716
- if (
717
- !currentRouteName ||
718
- isPublicRouteActive
719
- ) {
784
+ if (!currentRouteName || isPublicRouteActive) {
720
785
  return;
721
786
  }
722
787
 
723
788
  if (
724
789
  !isLogged ||
725
- Object.entries(currentCompany).length === 0 ||
726
- !deviceConfigFetched
790
+ !hasCurrentCompany ||
791
+ !deviceConfigFetched ||
792
+ !expectedTranslateBootstrapKey
727
793
  ) {
728
794
  return;
729
795
  }
730
796
 
731
797
  const currentConfig = JSON.parse(localStorage.getItem('config') || '{}');
732
- const sessionData = JSON.parse(localStorage.getItem('session') || '{}');
733
- const currentCompanyId = normalizeEntityId(currentCompany?.id);
734
- const defaultCompanyId = normalizeEntityId(defaultCompany?.id);
735
- const configuredLanguage = resolveConfiguredLanguage({
736
- currentCompany,
737
- defaultCompany,
738
- currentConfig,
739
- sessionData,
740
- });
741
- const nextTranslateBootstrapKey = [
742
- configuredLanguage,
743
- currentCompanyId,
744
- defaultCompanyId,
745
- ].join('::');
746
-
747
- if (translateBootstrapKeyRef.current === nextTranslateBootstrapKey) {
748
- if (global.t) {
749
- global.t.companies = companies;
750
- global.t.currentCompany = currentCompany;
751
- global.t.defaultCompany = defaultCompany;
752
- }
798
+
799
+ if (
800
+ translateBootstrapKeyRef.current === expectedTranslateBootstrapKey &&
801
+ global.t
802
+ ) {
803
+ global.t.companies = companies;
804
+ global.t.currentCompany = currentCompany;
805
+ global.t.defaultCompany = defaultCompany;
806
+ setActiveTranslateBootstrapKey(expectedTranslateBootstrapKey);
807
+ setTranslateReady(true);
753
808
 
754
809
  return;
755
810
  }
756
811
 
757
- if (currentConfig.language !== configuredLanguage) {
758
- const nextConfig = { ...currentConfig, language: configuredLanguage };
812
+ setTranslateReady(false);
813
+
814
+ if (currentConfig.language !== configuredTranslationLanguage) {
815
+ const nextConfig = {
816
+ ...currentConfig,
817
+ language: configuredTranslationLanguage,
818
+ };
759
819
  localStorage.setItem(
760
820
  'config',
761
821
  JSON.stringify(nextConfig),
762
822
  );
763
823
  }
764
824
 
765
- translateBootstrapKeyRef.current = nextTranslateBootstrapKey;
825
+ translateBootstrapKeyRef.current = expectedTranslateBootstrapKey;
766
826
  global.t = new Translate(
767
827
  companies,
768
828
  defaultCompany,
@@ -770,24 +830,38 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
770
830
  Object.keys(stores),
771
831
  translateStore,
772
832
  );
833
+ setActiveTranslateBootstrapKey(expectedTranslateBootstrapKey);
773
834
  setTranslateReady(true);
774
835
  global.refreshTranslationsUI?.();
775
836
  }, [
776
837
  companies,
838
+ configuredTranslationLanguage,
777
839
  currentCompany,
778
840
  currentRouteName,
779
841
  defaultCompany,
780
842
  deviceConfigFetched,
843
+ expectedTranslateBootstrapKey,
844
+ hasCurrentCompany,
781
845
  isLogged,
782
846
  isPublicRouteActive,
783
- translateActions,
847
+ translateStore,
848
+ ]);
849
+
850
+ useEffect(() => {
851
+ if (
852
+ !isLogged ||
853
+ !hasCurrentCompany ||
854
+ (currentRouteName && isTranslateBootstrapReady)
855
+ ) {
856
+ onBootstrapReady?.();
857
+ }
858
+ }, [
859
+ currentRouteName,
860
+ hasCurrentCompany,
861
+ isLogged,
862
+ isTranslateBootstrapReady,
863
+ onBootstrapReady,
784
864
  ]);
785
-
786
- useEffect(() => {
787
- if (!isLogged || translateReady || !hasCurrentCompany) {
788
- onBootstrapReady?.();
789
- }
790
- }, [isLogged, translateReady, hasCurrentCompany, onBootstrapReady]);
791
865
 
792
866
  useEffect(() => {
793
867
  if (device && device.id && isLogged) {
@@ -829,13 +903,9 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
829
903
 
830
904
  useEffect(() => {
831
905
  const fetchColors = async () => {
832
- const appDomain = resolveAppDomain(APP_ENV.DOMAIN);
833
- const params = appDomain ? { 'app-domain': appDomain } : undefined;
834
-
835
906
  try {
836
907
  const cssText = await api.fetch('themes-colors.css', {
837
908
  responseType: 'text',
838
- params,
839
909
  });
840
910
  const parsedColors = parseThemeCss(cssText);
841
911
  setBaseThemeColors(parsedColors);
@@ -875,6 +945,10 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
875
945
  isShopClientApp,
876
946
  ]);
877
947
 
948
+ if (requiresTranslateBootstrap && !isTranslateBootstrapReady) {
949
+ return <View style={providerStyles.loadingContainer} />;
950
+ }
951
+
878
952
  return (
879
953
  <ThemeContext.Provider value={themeContextValue}>
880
954
  <View
@@ -884,7 +958,7 @@ export const DefaultProvider = ({ children, onBootstrapReady }) => {
884
958
  backgroundColor: colors?.background || runtimeColors.background,
885
959
  },
886
960
  ]}>
887
- <View style={providerStyles.content}>{children}</View>
961
+ <View style={[providerStyles.content, runtimeUiScaleStyle]}>{children}</View>
888
962
  {!isShopClientApp && bottomNavigationCount === 0 && (
889
963
  <RuntimeInfoFooter
890
964
  appVersion={appVersion}
@@ -0,0 +1,249 @@
1
+ import React, {useMemo} from 'react';
2
+ import {
3
+ Image,
4
+ Modal,
5
+ ScrollView,
6
+ Text,
7
+ TouchableOpacity,
8
+ View,
9
+ } from 'react-native';
10
+ import Icon from 'react-native-vector-icons/Feather';
11
+
12
+ import styles from './NetworkPrinterPreviewModal.styles';
13
+
14
+ const tt = key => global.t?.t('configs', 'printPreview', key);
15
+
16
+ const clampColumns = value => {
17
+ const parsedValue = Number(String(value || '').replace(/\D+/g, ''));
18
+ if (!Number.isFinite(parsedValue) || parsedValue <= 0) {
19
+ return 48;
20
+ }
21
+
22
+ return Math.min(Math.max(parsedValue, 24), 80);
23
+ };
24
+
25
+ const safeText = value => String(value || '').trim();
26
+
27
+ const normalizePreviewLine = line => {
28
+ if (line && typeof line === 'object') {
29
+ return {
30
+ ...line,
31
+ text: safeText(line.text),
32
+ };
33
+ }
34
+
35
+ return {
36
+ text: safeText(line),
37
+ };
38
+ };
39
+
40
+ const centerLine = (value, columns) => {
41
+ const text = safeText(value);
42
+ if (text.length >= columns) {
43
+ return text.slice(0, columns);
44
+ }
45
+
46
+ const leftPad = Math.floor((columns - text.length) / 2);
47
+ return `${' '.repeat(leftPad)}${text}`;
48
+ };
49
+
50
+ const wrapLine = (value, columns) => {
51
+ const words = safeText(value).split(/\s+/).filter(Boolean);
52
+ const lines = [];
53
+ let currentLine = '';
54
+
55
+ words.forEach(word => {
56
+ if (!currentLine) {
57
+ currentLine = word;
58
+ return;
59
+ }
60
+
61
+ if (`${currentLine} ${word}`.length <= columns) {
62
+ currentLine = `${currentLine} ${word}`;
63
+ return;
64
+ }
65
+
66
+ lines.push(currentLine);
67
+ currentLine = word;
68
+ });
69
+
70
+ if (currentLine) {
71
+ lines.push(currentLine);
72
+ }
73
+
74
+ return lines.length > 0 ? lines : [''];
75
+ };
76
+
77
+ const wrapPreviewLine = (line, columns) => {
78
+ const normalizedLine = normalizePreviewLine(line);
79
+ return wrapLine(normalizedLine.text, columns).map(text => ({
80
+ ...normalizedLine,
81
+ text,
82
+ }));
83
+ };
84
+
85
+ const NetworkPrinterPreviewModal = ({
86
+ visible = false,
87
+ onClose,
88
+ codePage = '',
89
+ columns,
90
+ documents = [],
91
+ error = '',
92
+ logoUrl = '',
93
+ loading = false,
94
+ printerModel,
95
+ printerManufacturer,
96
+ subtitle = '',
97
+ title = '',
98
+ transport,
99
+ }) => {
100
+ const normalizedColumns = useMemo(() => clampColumns(columns), [columns]);
101
+ const normalizedDocuments = useMemo(
102
+ () => (Array.isArray(documents) ? documents : []),
103
+ [documents],
104
+ );
105
+
106
+ return (
107
+ <Modal
108
+ visible={visible}
109
+ transparent
110
+ animationType="fade"
111
+ onRequestClose={onClose}>
112
+ <View style={styles.backdrop}>
113
+ <View style={styles.panel}>
114
+ <View style={styles.header}>
115
+ <View style={styles.headerCopy}>
116
+ <Text style={styles.title}>{title || tt('title') || 'Visualizacao da impressao'}</Text>
117
+ <Text style={styles.subtitle}>
118
+ {subtitle ||
119
+ tt('subtitle') ||
120
+ 'Simulacao local para conferir largura, quebras e campos do cupom.'}
121
+ </Text>
122
+ </View>
123
+ <TouchableOpacity
124
+ style={styles.closeButton}
125
+ activeOpacity={0.85}
126
+ onPress={onClose}>
127
+ <Icon name="x" size={18} color="#0F172A" />
128
+ </TouchableOpacity>
129
+ </View>
130
+
131
+ <ScrollView contentContainerStyle={styles.content}>
132
+ <View style={styles.metaGrid}>
133
+ <View style={styles.metaChip}>
134
+ <Text style={styles.metaText}>
135
+ {`${normalizedColumns} ${tt('columns') || 'colunas'}`}
136
+ </Text>
137
+ </View>
138
+ <View style={styles.metaChip}>
139
+ <Text style={styles.metaText}>
140
+ {`${tt('codePage') || 'Code page'}: ${safeText(codePage) || 'cp850'}`}
141
+ </Text>
142
+ </View>
143
+ <View style={styles.metaChip}>
144
+ <Text style={styles.metaText}>
145
+ {safeText(transport) || tt('transportFallback') || 'tcp-raw'}
146
+ </Text>
147
+ </View>
148
+ <View style={styles.metaChip}>
149
+ <Text style={styles.metaText}>
150
+ {[printerManufacturer, printerModel]
151
+ .map(safeText)
152
+ .filter(Boolean)
153
+ .join(' ') || tt('modelMissing') || 'Modelo nao informado'}
154
+ </Text>
155
+ </View>
156
+ </View>
157
+
158
+ {loading ? (
159
+ <View style={styles.stateBox}>
160
+ <Text style={styles.stateText}>{tt('loadingRealData') || 'Carregando dados reais...'}</Text>
161
+ </View>
162
+ ) : error ? (
163
+ <View style={styles.errorBox}>
164
+ <Text style={styles.errorText}>{error}</Text>
165
+ </View>
166
+ ) : normalizedDocuments.length > 0 ? (
167
+ normalizedDocuments.map((document, index) => {
168
+ const lines = Array.isArray(document?.lines)
169
+ ? document.lines
170
+ : [];
171
+ const defaultHeaderLines = document?.hideDefaultHeader
172
+ ? []
173
+ : [
174
+ {
175
+ text: centerLine(
176
+ document?.title || `${tt('copy') || 'Via'} ${index + 1}`,
177
+ normalizedColumns,
178
+ ),
179
+ },
180
+ document?.subtitle
181
+ ? {text: centerLine(document.subtitle, normalizedColumns)}
182
+ : null,
183
+ {text: '-'.repeat(normalizedColumns)},
184
+ ].filter(Boolean);
185
+ const paperLines = [
186
+ ...defaultHeaderLines,
187
+ ...lines.flatMap(line => wrapPreviewLine(line, normalizedColumns)),
188
+ ].filter(Boolean);
189
+ const receiptWidth = normalizedColumns * 8;
190
+
191
+ return (
192
+ <View key={`${document?.title || 'preview'}-${index}`}>
193
+ {index > 0 ? (
194
+ <View style={styles.cutLine}>
195
+ <Text style={styles.cutText}>{tt('cutLine') || 'picote'}</Text>
196
+ </View>
197
+ ) : null}
198
+ <View style={styles.paper}>
199
+ <View style={[styles.receiptWrap, {width: receiptWidth}]}>
200
+ {logoUrl && !document?.hideLogo ? (
201
+ <View style={styles.logoWrap}>
202
+ <Image
203
+ source={{uri: logoUrl}}
204
+ style={styles.logo}
205
+ resizeMode="contain"
206
+ />
207
+ </View>
208
+ ) : null}
209
+ {paperLines.map((line, lineIndex) => (
210
+ <Text
211
+ key={`${document?.title || 'preview'}-${index}-${lineIndex}`}
212
+ style={[
213
+ styles.receiptText,
214
+ line.center ? styles.receiptTextCenter : null,
215
+ line.bold ? styles.receiptTextBold : null,
216
+ line.reverse ? styles.receiptTextReverse : null,
217
+ line.reverse && line.center
218
+ ? styles.receiptTextReverseCenter
219
+ : null,
220
+ line.large ? styles.receiptTextLarge : null,
221
+ ]}>
222
+ {line.text || ' '}
223
+ </Text>
224
+ ))}
225
+ </View>
226
+ </View>
227
+ </View>
228
+ );
229
+ })
230
+ ) : (
231
+ <View style={styles.stateBox}>
232
+ <Text style={styles.stateText}>
233
+ {tt('emptyData') || 'Nenhum dado real encontrado para visualizar.'}
234
+ </Text>
235
+ </View>
236
+ )}
237
+
238
+ <Text style={styles.hint}>
239
+ {tt('hint') ||
240
+ 'Esta visualizacao ainda nao envia dados para a impressora. Os blocos separados simulam os cortes de papel usados pelas filas.'}
241
+ </Text>
242
+ </ScrollView>
243
+ </View>
244
+ </View>
245
+ </Modal>
246
+ );
247
+ };
248
+
249
+ export default NetworkPrinterPreviewModal;