@controleonline/ui-common 1.2.70 → 1.2.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/.github/actions/workers/manager/action.yml +147 -0
  2. package/.github/actions/workers/qa/action.yml +44 -0
  3. package/.github/actions/workers/security/action.yml +44 -0
  4. package/.github/actions/workers/technical-documenter/action.yml +51 -0
  5. package/.github/workflows/manager-worker.yml +168 -0
  6. package/.github/workflows/publish-npm.yml +45 -0
  7. package/.gitmodules +3 -0
  8. package/AGENTS.md +6 -0
  9. package/README.md +14 -0
  10. package/package.json +32 -28
  11. package/src/api/fetch.js +1 -1
  12. package/src/api/index.js +328 -58
  13. package/src/react/components/AddImportModal.js +95 -111
  14. package/src/react/components/AnimatedModal.js +171 -0
  15. package/src/react/components/AnimatedModal.styles.js +21 -0
  16. package/src/react/components/AppTypeSwitcher.js +164 -0
  17. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  18. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  19. package/src/react/components/BottomNavigationBar.config.js +211 -231
  20. package/src/react/components/BottomNavigationBar.js +86 -31
  21. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  22. package/src/react/components/CategoryForm.js +52 -35
  23. package/src/react/components/DefaultProvider.native.js +194 -156
  24. package/src/react/components/DefaultProvider.web.js +188 -112
  25. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  26. package/src/react/components/DeviceAlertSoundService.js +3 -3
  27. package/src/react/components/KioskModeBridge.js +2 -2
  28. package/src/react/components/LauncherModeBridge.js +2 -2
  29. package/src/react/components/ManagerPushBridge.native.js +2 -2
  30. package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
  31. package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
  32. package/src/react/components/RemoteCheckoutService.js +28 -20
  33. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  34. package/src/react/components/RuntimeInfoFooter.js +137 -45
  35. package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
  36. package/src/react/components/StateStore.js +258 -0
  37. package/src/react/components/UnifiedPaymentBar.js +7 -4
  38. package/src/react/components/UserAvatar.js +160 -0
  39. package/src/react/components/WebsocketListener.native.js +11 -11
  40. package/src/react/components/integrations/IntegrationHero/index.js +48 -0
  41. package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
  42. package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
  43. package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
  44. package/src/react/config/deviceConfigBootstrap.js +71 -30
  45. package/src/react/css/orders.js +72 -0
  46. package/src/react/pages/Connections.js +229 -0
  47. package/src/react/pages/Connections.styles.js +222 -0
  48. package/src/react/pages/DeviceDetailPage.js +2814 -0
  49. package/src/react/pages/DeviceDetailPage.styles.js +308 -0
  50. package/src/react/pages/Devices/currentDevice.js +194 -0
  51. package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
  52. package/src/react/pages/Devices/deviceTypes/all.js +23 -0
  53. package/src/react/pages/Devices/deviceTypes/device.js +25 -0
  54. package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
  55. package/src/react/pages/Devices/deviceTypes/display.js +26 -0
  56. package/src/react/pages/Devices/deviceTypes/index.js +145 -0
  57. package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
  58. package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
  59. package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
  60. package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
  61. package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
  62. package/src/react/pages/Devices.js +183 -0
  63. package/src/react/pages/Devices.styles.js +353 -0
  64. package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
  65. package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
  66. package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
  67. package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
  68. package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
  69. package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
  70. package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
  71. package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
  72. package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
  73. package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
  74. package/src/react/pages/Food99IntegrationPage.js +1 -0
  75. package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
  76. package/src/react/pages/GenericLogPage.js +42 -167
  77. package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
  78. package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
  79. package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
  80. package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
  81. package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
  82. package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
  83. package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
  84. package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
  85. package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
  86. package/src/react/pages/IFoodIntegrationPage.js +1 -0
  87. package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
  88. package/src/react/pages/Imports.js +214 -166
  89. package/src/react/pages/IntegrationConfigPage.js +744 -0
  90. package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
  91. package/src/react/pages/Integrations.js +299 -0
  92. package/src/react/pages/Integrations.styles.js +130 -0
  93. package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
  94. package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
  95. package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
  96. package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
  97. package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
  98. package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
  99. package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
  100. package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
  101. package/src/react/pages/PrinterDeviceFormPage.js +521 -0
  102. package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
  103. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  104. package/src/react/pages/SettingsPage/index.js +1266 -1167
  105. package/src/react/pages/WhatsAppConnectionPage.js +459 -0
  106. package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
  107. package/src/react/pages/integrationsCatalog.js +399 -0
  108. package/src/react/print/providers/local.js +1 -1
  109. package/src/react/router/publicRoutes.js +25 -0
  110. package/src/react/services/Cielo/Checkout.js +39 -0
  111. package/src/react/services/Cielo/Cielo.js +193 -0
  112. package/src/react/services/Cielo/Print.js +7 -0
  113. package/src/react/services/InfinitePay/Checkout.js +33 -0
  114. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  115. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  116. package/src/react/styles/global.js +115 -0
  117. package/src/react/utils/categoryContexts.js +46 -0
  118. package/src/react/utils/entityDisplay.js +222 -217
  119. package/src/react/utils/fileUrl.js +201 -16
  120. package/src/react/utils/integrationPage.js +16 -0
  121. package/src/react/utils/menuNavigation.js +31 -0
  122. package/src/react/utils/orderIdentity.js +277 -0
  123. package/src/react/utils/remotePayment.js +115 -61
  124. package/src/react/utils/runtimeFooter.js +40 -22
  125. package/src/react/utils/runtimeLanguage.js +50 -9
  126. package/src/react/utils/runtimeMenu.js +190 -98
  127. package/src/react/utils/runtimeZoom.js +43 -0
  128. package/src/react/utils/shopConfig.js +72 -34
  129. package/src/react/utils/shopFranchises.js +182 -23
  130. package/src/react/utils/socketRuntimePipeline.js +14 -14
  131. package/src/react/utils/storeColumns.js +23 -4
  132. package/src/react/utils/userAvatar.js +104 -0
  133. package/src/store/categories/index.js +48 -20
  134. package/src/store/configs/index.js +2 -2
  135. package/src/store/device_config/index.js +62 -21
  136. package/src/store/entity_log/index.js +22 -1
  137. package/src/store/imports/customActions.js +51 -0
  138. package/src/store/imports/index.js +89 -88
  139. package/src/store/status/index.js +11 -11
  140. package/src/store/themes/index.js +22 -0
  141. package/src/store/timezones/index.js +3 -1
  142. package/src/tests/browser/manager/connections.spec.js +143 -0
  143. package/src/tests/browser/manager/devices-current.spec.js +343 -0
  144. package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
  145. package/src/tests/react/api/deviceHeaders.test.js +114 -0
  146. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  147. package/src/tests/react/components/AddImportModal.test.js +166 -0
  148. package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
  149. package/src/tests/react/components/UserAvatar.test.js +52 -0
  150. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  151. package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
  152. package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
  153. package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
  154. package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
  155. package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
  156. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  157. package/src/tests/react/services/Cielo.test.js +190 -0
  158. package/src/tests/react/store/categoriesOrdering.test.js +26 -0
  159. package/src/tests/react/utils/appDomain.test.js +62 -0
  160. package/src/tests/react/utils/entityDisplay.test.js +2 -2
  161. package/src/tests/react/utils/fileUrl.test.js +62 -0
  162. package/src/tests/react/utils/formatter.test.mjs +33 -0
  163. package/src/tests/react/utils/importStatus.test.js +2 -2
  164. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  165. package/src/tests/react/utils/remotePayment.test.js +62 -0
  166. package/src/tests/react/utils/runtimeFooter.test.js +32 -9
  167. package/src/tests/react/utils/runtimeLanguage.test.js +80 -10
  168. package/src/tests/react/utils/runtimeMenu.test.js +195 -111
  169. package/src/tests/react/utils/runtimeZoom.test.js +38 -0
  170. package/src/tests/react/utils/shopConfig.test.js +75 -0
  171. package/src/tests/react/utils/shopFranchises.test.js +122 -12
  172. package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
  173. package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
  174. package/src/tests/react/utils/translate.test.mjs +252 -396
  175. package/src/tests/react/utils/userAvatar.test.js +63 -0
  176. package/src/utils/appDomain.js +15 -4
  177. package/src/utils/formatter.js +122 -16
  178. package/src/utils/integrationConfigs.js +1 -0
  179. package/src/utils/translate.js +277 -398
  180. package/src/utils/translateDiscovery.js +96 -0
  181. package/src/utils/translateNormalize.js +33 -0
  182. package/src/utils/translatePending.js +92 -0
  183. package/src/utils/translateStorage.js +142 -0
  184. package/.github/agents/developer.agent.md +0 -30
  185. package/.github/agents/devops.agent.md +0 -30
  186. package/.github/agents/qa.agent.md +0 -30
  187. package/.github/agents/security.agent.md +0 -30
  188. package/.github/workflows/npmjs.yml +0 -35
@@ -1,28 +1,27 @@
1
- import React, {useEffect, useMemo, useState} from 'react';
2
- import {useNavigationState} from '@react-navigation/native';
3
- import {useStore} from '@store';
4
- import {api} from '@controleonline/ui-common/src/api';
5
- import {env as APP_ENV} from '@env';
6
- import {colors as runtimeColors} from '@controleonline/../../src/styles/colors';
7
- import {resolveThemePalette} from '@controleonline/../../src/styles/branding';
8
- import BottomNavigationBar from '@controleonline/ui-common/src/react/components/BottomNavigationBar';
1
+ import React, {useEffect, useMemo, useState} from 'react';
2
+ import {useNavigationState} from '@react-navigation/native';
3
+ import {useStore} from '@store';
4
+ import {api} from '@controleonline/ui-common/src/api';
5
+ import {app_type} from '@appType';
6
+ import BottomNavigationBar from '@controleonline/ui-common/src/react/components/BottomNavigationBar';
9
7
  import {
10
8
  getBottomNavigationPreset,
9
+ resolveBottomNavigationItems,
11
10
  resolveBottomNavigationRoute,
12
11
  } from '@controleonline/ui-common/src/react/components/BottomNavigationBar.config';
13
- import {
14
- filterRuntimeMenuModulesByType,
15
- normalizeAppType,
16
- normalizeRuntimeMenuResponse,
17
- resolveRuntimeMenuLabel,
18
- } from '@controleonline/ui-common/src/react/utils/runtimeMenu';
19
-
20
- const sortRuntimeMenuItems = (left, right) => {
21
- const orderDiff = Number(left?.sortOrder || 0) - Number(right?.sortOrder || 0);
22
- if (orderDiff !== 0) return orderDiff;
23
- return String(left?.label || '').localeCompare(String(right?.label || ''));
24
- };
25
-
12
+ import {
13
+ filterRuntimeMenuModulesByType,
14
+ normalizeAppType,
15
+ normalizeRuntimeMenuResponse,
16
+ resolveRuntimeMenuLabel,
17
+ } from '@controleonline/ui-common/src/react/utils/runtimeMenu';
18
+
19
+ const sortRuntimeMenuItems = (left, right) => {
20
+ const orderDiff = Number(left?.sortOrder || 0) - Number(right?.sortOrder || 0);
21
+ if (orderDiff !== 0) return orderDiff;
22
+ return String(left?.label || '').localeCompare(String(right?.label || ''));
23
+ };
24
+
26
25
  const RuntimeBottomNavigationBar = ({
27
26
  activeRouteName: activeRouteNameProp,
28
27
  colors: colorsOverride,
@@ -31,14 +30,15 @@ const RuntimeBottomNavigationBar = ({
31
30
  itemMapper,
32
31
  menuType = 'toolbar',
33
32
  navigation,
34
- presetKey,
35
- routeAliases: routeAliasesProp,
36
- testID = 'bottom-navigation',
37
- }) => {
38
- const state = useNavigationState(current => current);
39
- const activeRouteName =
40
- activeRouteNameProp || state?.routes?.[state.index]?.name || 'HomePage';
41
-
33
+ presetKey,
34
+ routeAliases: routeAliasesProp,
35
+ testID = 'bottom-navigation',
36
+ useModernWebChromeProps = false,
37
+ }) => {
38
+ const state = useNavigationState(current => current);
39
+ const activeRouteName =
40
+ activeRouteNameProp || state?.routes?.[state.index]?.name || 'HomePage';
41
+
42
42
  const themeStore = useStore('theme');
43
43
  const peopleStore = useStore('people');
44
44
  const themeGetters = themeStore?.getters || {};
@@ -48,7 +48,7 @@ const RuntimeBottomNavigationBar = ({
48
48
  const [runtimeMenus, setRuntimeMenus] = useState([]);
49
49
  const preset = presetKey ? getBottomNavigationPreset(presetKey) : null;
50
50
  const routeAliases = routeAliasesProp || preset?.routeAliases || {};
51
- const appType = normalizeAppType(APP_ENV.APP_TYPE);
51
+ const appType = normalizeAppType(app_type);
52
52
 
53
53
  useEffect(() => {
54
54
  if (!currentCompany?.id || !menuType) {
@@ -56,96 +56,123 @@ const RuntimeBottomNavigationBar = ({
56
56
  return undefined;
57
57
  }
58
58
 
59
- let cancelled = false;
60
- setRuntimeMenus([]);
59
+ if (preset) {
60
+ const presetMenus = Array.isArray(preset?.items)
61
+ ? resolveBottomNavigationItems(
62
+ preset.items.map((item, index) => ({
63
+ ...item,
64
+ menuType,
65
+ sortOrder: Number(item?.sortOrder || (index + 1) * 10),
66
+ })),
67
+ global.t?.t,
68
+ )
69
+ : [];
61
70
 
62
- api
63
- .fetch('menus-people', {
64
- params: {
65
- myCompany: currentCompany.id,
66
- appType,
67
- menuType,
71
+ setRuntimeMenus([
72
+ {
73
+ id: presetKey || 'runtime-bottom-navigation',
74
+ label: preset?.label || presetKey || '',
75
+ icon: preset?.icon || '',
76
+ menus: presetMenus,
68
77
  },
69
- })
70
- .then(result => {
71
- if (!cancelled) {
72
- setRuntimeMenus(
73
- normalizeRuntimeMenuResponse(result, {
74
- appType,
75
- allowFallback: false,
76
- }),
77
- );
78
- }
79
- })
80
- .catch(() => {
81
- if (!cancelled) {
82
- setRuntimeMenus([]);
83
- }
84
- });
85
-
86
- return () => {
87
- cancelled = true;
88
- };
89
- }, [appType, currentCompany?.id, menuType]);
90
-
91
- const colors = useMemo(() => {
92
- if (colorsOverride) {
93
- return colorsOverride;
78
+ ]);
79
+ return undefined;
94
80
  }
95
81
 
96
- return resolveThemePalette(
97
- {
98
- ...themeColors,
99
- ...(currentCompany?.theme?.colors || {}),
100
- },
101
- runtimeColors,
102
- );
103
- }, [colorsOverride, currentCompany?.theme?.colors, themeColors]);
104
-
105
- const navItems = useMemo(() => {
106
- const mapper = typeof itemMapper === 'function' ? itemMapper : item => item;
107
- const filter = typeof itemFilter === 'function' ? itemFilter : () => true;
108
-
109
- return filterRuntimeMenuModulesByType(runtimeMenus, menuType)
110
- .flatMap(module => (Array.isArray(module?.menus) ? module.menus : []))
111
- .map(item => mapper({...item}))
112
- .filter(Boolean)
113
- .filter(filter)
114
- .sort(sortRuntimeMenuItems)
115
- .map(item => ({
116
- route: item.route,
117
- icon: item.icon || 'circle',
118
- label: resolveRuntimeMenuLabel(item, global.t?.t),
119
- routeParams: item.routeParams,
120
- menuType: item.menuType,
121
- }))
122
- .filter(item => Boolean(item.route));
123
- }, [itemFilter, itemMapper, menuType, runtimeMenus]);
124
-
125
- const resolvedActiveRoute = resolveBottomNavigationRoute(
126
- routeAliases,
127
- activeRouteName,
128
- );
129
- const knownRoute = navItems.some(item => item.route === resolvedActiveRoute);
130
- const effectiveActiveRoute = knownRoute
131
- ? resolvedActiveRoute
132
- : navItems[0]?.route || activeRouteName;
133
-
134
- const isDisabled =
135
- typeof disabledOverride === 'boolean'
136
- ? disabledOverride
137
- : !currentCompany || Object.keys(currentCompany).length === 0;
138
-
139
- return (
140
- <BottomNavigationBar
141
- activeRouteName={effectiveActiveRoute}
142
- colors={colors}
143
- disabled={isDisabled}
144
- items={navItems}
145
- navigation={navigation}
146
- testID={testID}
147
- />
148
- );
149
- };
150
-
151
- export default RuntimeBottomNavigationBar;
82
+ let cancelled = false;
83
+ setRuntimeMenus([]);
84
+
85
+ api
86
+ .fetch('menus-people', {
87
+ params: {
88
+ myCompany: currentCompany.id,
89
+ appType,
90
+ menuType,
91
+ },
92
+ })
93
+ .then(result => {
94
+ if (!cancelled) {
95
+ setRuntimeMenus(
96
+ normalizeRuntimeMenuResponse(result, {
97
+ appType,
98
+ allowFallback: false,
99
+ }),
100
+ );
101
+ }
102
+ })
103
+ .catch(() => {
104
+ if (!cancelled) {
105
+ setRuntimeMenus([]);
106
+ }
107
+ });
108
+
109
+ return () => {
110
+ cancelled = true;
111
+ };
112
+ }, [
113
+ appType,
114
+ currentCompany?.id,
115
+ menuType,
116
+ preset,
117
+ presetKey,
118
+ ]);
119
+
120
+ const colors = useMemo(() => {
121
+ if (colorsOverride) {
122
+ return colorsOverride;
123
+ }
124
+
125
+ return {
126
+ ...themeColors,
127
+ ...(currentCompany?.theme?.colors || {}),
128
+ };
129
+ }, [colorsOverride, currentCompany?.theme?.colors, themeColors]);
130
+
131
+ const navItems = useMemo(() => {
132
+ const mapper = typeof itemMapper === 'function' ? itemMapper : item => item;
133
+ const filter = typeof itemFilter === 'function' ? itemFilter : () => true;
134
+
135
+ return filterRuntimeMenuModulesByType(runtimeMenus, menuType)
136
+ .flatMap(module => (Array.isArray(module?.menus) ? module.menus : []))
137
+ .map(item => mapper({...item}))
138
+ .filter(Boolean)
139
+ .filter(filter)
140
+ .sort(sortRuntimeMenuItems)
141
+ .map(item => ({
142
+ route: item.route,
143
+ icon: item.icon || 'circle',
144
+ label: resolveRuntimeMenuLabel(item, global.t?.t),
145
+ routeParams: item.routeParams,
146
+ menuType: item.menuType,
147
+ }))
148
+ .filter(item => Boolean(item.route));
149
+ }, [itemFilter, itemMapper, menuType, runtimeMenus]);
150
+
151
+ const resolvedActiveRoute = resolveBottomNavigationRoute(
152
+ routeAliases,
153
+ activeRouteName,
154
+ );
155
+ const knownRoute = navItems.some(item => item.route === resolvedActiveRoute);
156
+ const effectiveActiveRoute = knownRoute
157
+ ? resolvedActiveRoute
158
+ : navItems[0]?.route || activeRouteName;
159
+
160
+ const isDisabled =
161
+ typeof disabledOverride === 'boolean'
162
+ ? disabledOverride
163
+ : !currentCompany || Object.keys(currentCompany).length === 0;
164
+
165
+ return (
166
+ <BottomNavigationBar
167
+ activeRouteName={effectiveActiveRoute}
168
+ colors={colors}
169
+ disabled={isDisabled}
170
+ items={navItems}
171
+ navigation={navigation}
172
+ testID={testID}
173
+ useModernWebChromeProps={useModernWebChromeProps}
174
+ />
175
+ );
176
+ };
177
+
178
+ export default RuntimeBottomNavigationBar;
@@ -1,28 +1,43 @@
1
- import React, {useEffect, useMemo, useState} from 'react';
2
- import {ActivityIndicator, Text, View, useWindowDimensions} from 'react-native';
1
+ import React, {useEffect, useMemo, useRef, useState} from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ Animated,
5
+ Text,
6
+ View,
7
+ useWindowDimensions,
8
+ } from 'react-native';
3
9
  import {useStore, useStores} from '@store';
4
10
  import {useSafeAreaInsets} from 'react-native-safe-area-context';
5
- import {colors as runtimeColors} from '@controleonline/../../src/styles/colors';
6
11
  import {
7
12
  DEVICE_RUNTIME_DEBUG_INFO_ENABLED_KEY,
8
13
  isTruthyValue,
9
14
  parseConfigsObject,
10
15
  } from '@controleonline/ui-common/src/react/config/deviceConfigBootstrap';
11
16
  import {
12
- getRuntimeFooterDebugInfo,
13
- getRuntimeFooterPrimaryText,
14
- getRuntimeFooterText,
15
- } from '@controleonline/ui-common/src/react/utils/runtimeFooter';
16
- import styles from './RuntimeInfoFooter.styles';
17
-
18
- const ROTATION_INTERVAL_MS = 6000;
19
- const COMPACT_BREAKPOINT = 720;
20
- const MAX_INLINE_TEXT_LENGTH = 84;
17
+ getRuntimeFooterDebugInfo,
18
+ getRuntimeFooterPrimaryText,
19
+ getRuntimeFooterRotationEntries,
20
+ getRuntimeFooterText,
21
+ getRuntimeFooterTextLines,
22
+ } from '@controleonline/ui-common/src/react/utils/runtimeFooter';
23
+ import styles from './RuntimeInfoFooter.styles';
24
+
25
+ const ROTATION_INTERVAL_MS = 6000;
26
+ const FADE_DURATION_MS = 260;
27
+ const COMPACT_BREAKPOINT = 720;
28
+ const MAX_INLINE_TEXT_LENGTH = 84;
21
29
 
22
- const RuntimeInfoFooter = ({appVersion, defaultCompany, device, colors}) => {
30
+ const RuntimeInfoFooter = ({
31
+ appVersion,
32
+ defaultCompany,
33
+ device,
34
+ colors,
35
+ useModernWebChromeProps = false,
36
+ }) => {
23
37
  const {width} = useWindowDimensions();
24
38
  const insets = useSafeAreaInsets();
25
39
  const [activeIndex, setActiveIndex] = useState(0);
40
+ const fadeOpacity = useRef(new Animated.Value(1)).current;
26
41
  const allStores = useStores(state => state);
27
42
  const deviceConfigStore = useStore('device_config');
28
43
  const runtimeDebugStore = useStore('runtime_debug');
@@ -48,10 +63,14 @@ const RuntimeInfoFooter = ({appVersion, defaultCompany, device, colors}) => {
48
63
  }),
49
64
  [appVersion, device, deviceConfigItem, footerDebugInfo.primaryText],
50
65
  );
51
- const companyFooterText = useMemo(
52
- () => getRuntimeFooterText(defaultCompany),
53
- [defaultCompany?.configs],
54
- );
66
+ const companyFooterText = useMemo(
67
+ () => getRuntimeFooterText(defaultCompany),
68
+ [defaultCompany?.configs],
69
+ );
70
+ const footerTextLines = useMemo(
71
+ () => getRuntimeFooterTextLines(companyFooterText),
72
+ [companyFooterText],
73
+ );
55
74
  const deviceConfigs = useMemo(
56
75
  () => parseConfigsObject(deviceConfigItem?.configs),
57
76
  [deviceConfigItem?.configs],
@@ -64,15 +83,26 @@ const RuntimeInfoFooter = ({appVersion, defaultCompany, device, colors}) => {
64
83
  [deviceConfigs],
65
84
  );
66
85
 
67
- const entries = useMemo(
68
- () => [primaryText, companyFooterText].filter(Boolean),
69
- [companyFooterText, primaryText],
70
- );
71
- const inlineText = useMemo(() => entries.join(' • '), [entries]);
72
- const shouldRotate =
73
- !showDebugInfo &&
74
- entries.length > 1 &&
75
- (width < COMPACT_BREAKPOINT || inlineText.length > MAX_INLINE_TEXT_LENGTH);
86
+ const rotationEntries = useMemo(
87
+ () =>
88
+ getRuntimeFooterRotationEntries({
89
+ companyFooterText,
90
+ primaryText,
91
+ }),
92
+ [companyFooterText, primaryText],
93
+ );
94
+ const inlineText = useMemo(
95
+ () => [primaryText, ...footerTextLines].filter(Boolean).join(' • '),
96
+ [footerTextLines, primaryText],
97
+ );
98
+ const shouldRotate =
99
+ !showDebugInfo &&
100
+ rotationEntries.length > 1 &&
101
+ (
102
+ footerTextLines.length > 1 ||
103
+ width < COMPACT_BREAKPOINT ||
104
+ inlineText.length > MAX_INLINE_TEXT_LENGTH
105
+ );
76
106
  const footerEntries = useMemo(
77
107
  () =>
78
108
  Object.values(runtimeDebugSummary?.entries || {})
@@ -110,7 +140,26 @@ const RuntimeInfoFooter = ({appVersion, defaultCompany, device, colors}) => {
110
140
  () => footerEntries.flatMap(entry => entry?.lines || []),
111
141
  [footerEntries],
112
142
  );
113
- const socketIndicatorColor = socketEntry?.indicatorColor;
143
+ const socketIndicatorColor = useMemo(() => {
144
+ const indicatorTone = String(socketEntry?.indicatorTone || '').trim();
145
+ if (!indicatorTone) {
146
+ return undefined;
147
+ }
148
+
149
+ if (indicatorTone === 'success') {
150
+ return colors?.success;
151
+ }
152
+
153
+ if (indicatorTone === 'warning') {
154
+ return colors?.warning;
155
+ }
156
+
157
+ if (indicatorTone === 'error') {
158
+ return colors?.error;
159
+ }
160
+
161
+ return undefined;
162
+ }, [colors?.error, colors?.success, colors?.warning, socketEntry?.indicatorTone]);
114
163
  const hasStoreLoading = useMemo(
115
164
  () =>
116
165
  Object.values(allStores || {}).some(
@@ -123,32 +172,72 @@ const RuntimeInfoFooter = ({appVersion, defaultCompany, device, colors}) => {
123
172
  const bottomInset = Math.max(Number(insets.bottom) || 0, 16);
124
173
 
125
174
  useEffect(() => {
126
- if (!shouldRotate || entries.length <= 1) {
175
+ if (!shouldRotate || rotationEntries.length <= 1) {
127
176
  setActiveIndex(0);
177
+ fadeOpacity.stopAnimation();
178
+ fadeOpacity.setValue(1);
128
179
  return;
129
180
  }
130
-
131
- const intervalId = setInterval(() => {
132
- setActiveIndex(current => (current + 1) % entries.length);
133
- }, ROTATION_INTERVAL_MS);
134
-
181
+
182
+ let timeoutId;
183
+ let cancelled = false;
184
+
185
+ const scheduleTransition = () => {
186
+ timeoutId = setTimeout(() => {
187
+ Animated.timing(fadeOpacity, {
188
+ toValue: 0,
189
+ duration: FADE_DURATION_MS,
190
+ useNativeDriver: true,
191
+ }).start(({finished}) => {
192
+ if (!finished || cancelled) {
193
+ return;
194
+ }
195
+
196
+ setActiveIndex(current => (current + 1) % rotationEntries.length);
197
+
198
+ Animated.timing(fadeOpacity, {
199
+ toValue: 1,
200
+ duration: FADE_DURATION_MS,
201
+ useNativeDriver: true,
202
+ }).start(({finished: fadeInFinished}) => {
203
+ if (fadeInFinished && !cancelled) {
204
+ scheduleTransition();
205
+ }
206
+ });
207
+ });
208
+ }, ROTATION_INTERVAL_MS);
209
+ };
210
+
211
+ fadeOpacity.setValue(1);
212
+ scheduleTransition();
213
+
135
214
  return () => {
136
- clearInterval(intervalId);
215
+ cancelled = true;
216
+ if (timeoutId) {
217
+ clearTimeout(timeoutId);
218
+ }
219
+ fadeOpacity.stopAnimation();
137
220
  };
138
- }, [entries.length, shouldRotate]);
221
+ }, [fadeOpacity, rotationEntries.length, shouldRotate]);
139
222
 
140
- if (entries.length === 0 && !showDebugInfo) {
223
+ if (rotationEntries.length === 0 && !showDebugInfo) {
141
224
  return null;
142
225
  }
143
-
144
- const displayedText = shouldRotate ? entries[activeIndex] : inlineText;
145
- const backgroundColor = colors?.background || runtimeColors.background;
146
- const borderColor = colors?.border || runtimeColors.border;
147
- const textColor = colors?.textSecondary || runtimeColors.textSecondary;
148
- const loadingColor = colors?.primary || runtimeColors.primary || textColor;
226
+
227
+ const displayedText = shouldRotate
228
+ ? rotationEntries[activeIndex]
229
+ : inlineText;
230
+ const backgroundColor = colors?.footerBackground;
231
+ const borderColor = colors?.footerBorder;
232
+ const textColor = colors?.footerText;
233
+ const loadingColor = colors?.footerLink;
234
+ const shellProps = useModernWebChromeProps ? {} : {pointerEvents: 'none'};
235
+ const shellStyle = useModernWebChromeProps
236
+ ? [styles.shell, {pointerEvents: 'none'}]
237
+ : styles.shell;
149
238
 
150
239
  return (
151
- <View pointerEvents="none" style={styles.shell}>
240
+ <View {...shellProps} style={shellStyle}>
152
241
  <View
153
242
  testID="runtime-info-footer"
154
243
  style={[
@@ -171,18 +260,21 @@ const RuntimeInfoFooter = ({appVersion, defaultCompany, device, colors}) => {
171
260
  ]}
172
261
  />
173
262
  </View>
174
- <Text
263
+ <Animated.Text
175
264
  numberOfLines={1}
176
265
  ellipsizeMode="tail"
177
266
  minimumFontScale={0.85}
178
267
  style={[
179
268
  styles.primaryText,
269
+ {
270
+ opacity: showDebugInfo ? 1 : fadeOpacity,
271
+ },
180
272
  {
181
273
  color: textColor,
182
274
  },
183
275
  ]}>
184
276
  {showDebugInfo ? inlineText || primaryText || device?.id || '--' : displayedText}
185
- </Text>
277
+ </Animated.Text>
186
278
  <View style={styles.loadingWrap}>
187
279
  {hasStoreLoading && (
188
280
  <ActivityIndicator color={loadingColor} size="small" />
@@ -18,15 +18,18 @@ const styles = StyleSheet.create({
18
18
  flexDirection: 'row',
19
19
  alignItems: 'center',
20
20
  justifyContent: 'center',
21
+ minHeight: 20,
21
22
  },
22
23
  statusDotWrap: {
23
24
  minWidth: 22,
25
+ minHeight: 20,
24
26
  marginRight: 8,
25
27
  alignItems: 'center',
26
28
  justifyContent: 'center',
27
29
  },
28
30
  loadingWrap: {
29
31
  minWidth: 22,
32
+ minHeight: 20,
30
33
  marginLeft: 8,
31
34
  alignItems: 'center',
32
35
  justifyContent: 'center',