@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,12 +1,40 @@
1
- const TRANSLATES_STORAGE_KEY = "translates";
1
+ import {
2
+ formatMessage,
3
+ isNonEmptyMessage,
4
+ normalizeId,
5
+ normalizeLanguageCode,
6
+ resolveReferenceId,
7
+ } from "./translateNormalize.js";
8
+ import {
9
+ TRANSLATES_STORAGE_KEY,
10
+ collectCompaniesToCache,
11
+ getCompanyBucket as readCompanyBucket,
12
+ getLanguageBucket as readLanguageBucket,
13
+ getStoreBucket as readStoreBucket,
14
+ loadStorageObject,
15
+ writeMessageToBucket,
16
+ } from "./translateStorage.js";
17
+ import {
18
+ getPendingMessages as readPendingMessages,
19
+ getQueuedTranslateGroups as readQueuedGroups,
20
+ getQueuedTranslateGroupsForStore as readQueuedGroupsForStore,
21
+ hasPendingTranslate as readHasPendingTranslate,
22
+ } from "./translatePending.js";
23
+ import {
24
+ discoveryAll as runDiscoveryAll,
25
+ discoveryStoreTranslate as runDiscoveryStoreTranslate,
26
+ fetchTranslates as runFetchTranslates,
27
+ resolveStoreTranslates as runResolveStoreTranslates,
28
+ } from "./translateDiscovery.js";
2
29
 
3
30
  export default class Translate {
4
31
  constructor(companies, defaultCompany, currentCompany, stores, translateStore) {
5
- this.translates = this.loadStorageObject(TRANSLATES_STORAGE_KEY);
32
+ this.translates = loadStorageObject(TRANSLATES_STORAGE_KEY);
6
33
 
7
- this.language = this.normalizeLanguageCode(
8
- JSON.parse(localStorage.getItem("config") || "{}").language
9
- ) || "pt-br";
34
+ this.language =
35
+ normalizeLanguageCode(
36
+ JSON.parse(localStorage.getItem("config") || "{}").language,
37
+ ) || "pt-br";
10
38
 
11
39
  this.defaultCompany = defaultCompany;
12
40
  this.currentCompany = currentCompany;
@@ -17,6 +45,8 @@ export default class Translate {
17
45
  this.bootstrapStores = new Set(this.getStoreList());
18
46
  this.discoveredStores = new Set();
19
47
  this.pendingStoreDiscoveries = new Map();
48
+ this.pendingMissingTranslateSchedules = new Set();
49
+ this.pendingTranslateResolutionPromise = null;
20
50
  this.pendingPersistRequests = new Map();
21
51
  this.t = this.t.bind(this);
22
52
  if (typeof this.translateActions?.setPendingMessages === "function") {
@@ -26,194 +56,101 @@ export default class Translate {
26
56
  this.hydrateDiscoveredStores();
27
57
  }
28
58
 
29
- loadStorageObject(key) {
30
- if (typeof localStorage === "undefined") {
31
- return {};
32
- }
59
+ normalizeId(value) {
60
+ return normalizeId(value);
61
+ }
33
62
 
34
- try {
35
- const value = localStorage.getItem(key);
36
- if (!value) return {};
63
+ normalizeLanguageCode(value) {
64
+ return normalizeLanguageCode(value);
65
+ }
37
66
 
38
- const parsed = JSON.parse(value);
39
- return parsed && typeof parsed === "object" ? parsed : {};
40
- } catch {
41
- return {};
42
- }
67
+ formatMessage(key) {
68
+ return formatMessage(key);
43
69
  }
44
-
45
- getLanguageBucket(language = this.language, createIfMissing = false) {
46
- const normalizedLanguage = this.normalizeLanguageCode(language);
47
- if (!normalizedLanguage) return null;
48
70
 
49
- if (!this.translates[normalizedLanguage] && createIfMissing) {
50
- this.translates[normalizedLanguage] = {};
71
+ resolveReferenceId(value) {
72
+ if (value == null) return null;
73
+ if (typeof value === "object") {
74
+ return this.normalizeId(
75
+ value.id || value["@id"] || value.people || value.language,
76
+ );
51
77
  }
78
+ return this.normalizeId(value);
79
+ }
52
80
 
53
- return this.translates[normalizedLanguage] || null;
81
+ getLanguageBucket(language = this.language, createIfMissing = false) {
82
+ return readLanguageBucket(this.translates, language, createIfMissing);
54
83
  }
55
84
 
56
85
  getCompanyBucket(companyId, createIfMissing = false, language = this.language) {
57
- const normalizedCompanyId = this.normalizeId(companyId);
58
- if (!normalizedCompanyId) return null;
59
-
60
- const languageBucket = this.getLanguageBucket(language, createIfMissing);
61
- if (!languageBucket) return null;
62
-
63
- if (!languageBucket.companies && createIfMissing) {
64
- languageBucket.companies = {};
65
- }
66
-
67
- if (
68
- createIfMissing &&
69
- languageBucket.companies &&
70
- !languageBucket.companies[normalizedCompanyId]
71
- ) {
72
- languageBucket.companies[normalizedCompanyId] = {};
73
- }
74
-
75
- return languageBucket.companies?.[normalizedCompanyId] || null;
86
+ return readCompanyBucket(
87
+ this.translates,
88
+ companyId,
89
+ createIfMissing,
90
+ language,
91
+ );
76
92
  }
77
93
 
78
94
  getStoreBucket(companyId, store, createIfMissing = false, language = this.language) {
79
- if (!store) return null;
80
-
81
- if (companyId) {
82
- const companyBucket = this.getCompanyBucket(companyId, createIfMissing, language);
83
- if (!companyBucket) return null;
84
-
85
- if (createIfMissing && !companyBucket[store]) {
86
- companyBucket[store] = {};
87
- }
88
-
89
- return companyBucket[store] || null;
90
- }
91
-
92
- const languageBucket = this.getLanguageBucket(language, createIfMissing);
93
- if (!languageBucket) return null;
94
-
95
- if (createIfMissing && !languageBucket[store]) {
96
- languageBucket[store] = {};
97
- }
98
-
99
- return languageBucket[store] || null;
100
- }
101
-
102
- normalizeId(value) {
103
- if (value == null) return null;
104
-
105
- const match = String(value).match(/\d+/);
106
- return match?.[0] || null;
107
- }
108
-
109
- normalizeLanguageCode(value) {
110
- if (typeof value !== "string") return "";
111
-
112
- return value.trim().replace(/_/g, "-").toLowerCase();
113
- }
114
-
115
- getCompaniesToCache() {
116
- const candidates = [
117
- this.defaultCompany,
118
- this.currentCompany,
119
- ].filter((company) => company?.id);
120
-
121
- const unique = [];
122
- const seen = new Set();
123
-
124
- candidates.forEach((company) => {
125
- const companyId = this.normalizeId(company.id);
126
- if (!companyId || seen.has(companyId)) return;
127
-
128
- seen.add(companyId);
129
- unique.push(company);
130
- });
131
-
132
- return unique;
133
- }
134
-
135
- getStoreList() {
136
- if (Array.isArray(this.stores)) {
137
- return this.stores.filter(Boolean);
138
- }
139
-
140
- return this.stores ? [this.stores] : [];
95
+ return readStoreBucket(
96
+ this.translates,
97
+ companyId,
98
+ store,
99
+ createIfMissing,
100
+ language,
101
+ );
141
102
  }
142
103
 
143
- getPendingMessages() {
144
- const messages = this.translateStore?.getters?.pendingMessages;
145
- if (!messages || typeof messages !== "object" || Array.isArray(messages)) {
146
- return {};
147
- }
148
-
149
- return messages;
104
+ getCompaniesToCache() {
105
+ return collectCompaniesToCache(this.defaultCompany, this.currentCompany);
150
106
  }
151
107
 
152
- getPendingLanguageBucket(language = this.language) {
153
- const pendingMessages = this.getPendingMessages();
154
- const normalizedLanguage = this.normalizeLanguageCode(language);
155
- if (!normalizedLanguage) return null;
156
-
157
- return pendingMessages[normalizedLanguage] || null;
108
+ // Always load current + main company so runtime resolution can apply
109
+ // current company -> main company -> global fallback (getMessageFromBuckets).
110
+ getResolveCompaniesToCache() {
111
+ return this.getCompaniesToCache();
158
112
  }
159
113
 
160
- getPendingCompanyBucket(companyId, language = this.language) {
161
- const normalizedCompanyId = this.normalizeId(companyId);
162
- if (!normalizedCompanyId) return null;
163
-
164
- const languageBucket = this.getPendingLanguageBucket(language);
165
- if (!languageBucket) return null;
166
-
167
- const companiesBucket = languageBucket.companies;
168
- if (!companiesBucket || typeof companiesBucket !== "object") {
169
- return null;
114
+ getStoreList() {
115
+ if (Array.isArray(this.stores)) {
116
+ return this.stores.filter(Boolean);
170
117
  }
171
-
172
- return companiesBucket[normalizedCompanyId] || null;
118
+ return this.stores ? [this.stores] : [];
173
119
  }
174
120
 
175
- getPendingStoreBucket(companyId, store, language = this.language) {
176
- const normalizedStore = String(store || "").trim();
177
- if (!normalizedStore) return null;
178
-
179
- const companyBucket = this.getPendingCompanyBucket(companyId, language);
180
- if (!companyBucket) return null;
181
-
182
- return companyBucket[normalizedStore] || null;
121
+ getPendingMessages() {
122
+ return readPendingMessages(this.translateStore);
183
123
  }
184
124
 
185
125
  hasPendingTranslate(store, type, key) {
186
- const pendingStoreBucket = this.getPendingStoreBucket(
126
+ return readHasPendingTranslate(
127
+ this.translateStore,
187
128
  this.defaultCompany?.id,
188
129
  store,
130
+ type,
131
+ key,
189
132
  this.language,
190
133
  );
191
-
192
- return Boolean(pendingStoreBucket?.[type]?.[key]);
193
134
  }
194
135
 
195
136
  getStoreDiscoveryToken(store) {
196
- return [
197
- this.language,
198
- String(store || ""),
199
- ].join("::");
137
+ return `discover:${store}:${this.language}`;
200
138
  }
201
139
 
202
140
  canDiscoverStore() {
203
- return typeof this.translateActions?.getItems === "function";
141
+ return typeof this.translateActions?.resolveQueuedMessages === "function";
204
142
  }
205
143
 
206
144
  hasCachedBootstrapStore(store) {
207
- if (!store) return false;
208
-
209
145
  const companies = this.getCompaniesToCache();
210
146
  if (companies.length === 0) return false;
211
-
212
- return companies.every((company) => this.getStoreBucket(company.id, store) != null);
147
+ return companies.every(
148
+ (company) => this.getStoreBucket(company.id, store) != null,
149
+ );
213
150
  }
214
151
 
215
152
  hydrateDiscoveredStores() {
216
- this.bootstrapStores.forEach((store) => {
153
+ this.getStoreList().forEach((store) => {
217
154
  if (this.hasCachedBootstrapStore(store)) {
218
155
  this.markStoreDiscovered(store);
219
156
  }
@@ -221,86 +158,55 @@ export default class Translate {
221
158
  }
222
159
 
223
160
  hasDiscoveredStore(store) {
224
- if (!store) return false;
225
-
226
- return this.discoveredStores.has(this.getStoreDiscoveryToken(store));
161
+ return this.discoveredStores.has(store);
227
162
  }
228
163
 
229
164
  markStoreDiscovered(store) {
230
- if (!store) return;
231
-
232
- this.discoveredStores.add(this.getStoreDiscoveryToken(store));
165
+ if (store) this.discoveredStores.add(store);
233
166
  }
234
167
 
235
168
  ensureStoreDiscovered(store) {
236
- if (!store || !this.canDiscoverStore()) {
237
- return Promise.resolve(this.translates);
238
- }
239
-
240
- if (this.hasDiscoveredStore(store)) {
169
+ if (!store || this.hasDiscoveredStore(store) || !this.canDiscoverStore()) {
241
170
  return Promise.resolve(this.translates);
242
171
  }
243
172
 
244
- const token = this.getStoreDiscoveryToken(store);
245
- if (this.pendingStoreDiscoveries.has(token)) {
246
- return this.pendingStoreDiscoveries.get(token);
173
+ if (this.pendingStoreDiscoveries.has(store)) {
174
+ return this.pendingStoreDiscoveries.get(store);
247
175
  }
248
176
 
249
- const discoveryPromise = Promise.resolve()
250
- .then(() => this.discoveryStoreTranslate(store))
177
+ const discoveryPromise = this.discoveryStoreTranslate(store)
178
+ .catch(() => this.translates)
251
179
  .finally(() => {
252
- this.pendingStoreDiscoveries.delete(token);
180
+ this.pendingStoreDiscoveries.delete(store);
253
181
  });
254
182
 
255
- this.pendingStoreDiscoveries.set(token, discoveryPromise);
256
-
183
+ this.pendingStoreDiscoveries.set(store, discoveryPromise);
257
184
  return discoveryPromise;
258
185
  }
259
186
 
260
187
  notifyTranslationsUpdated() {
261
- const globalObject =
262
- typeof global !== "undefined" ? global : globalThis;
263
-
264
- if (typeof globalObject?.refreshTranslationsUI === "function") {
265
- globalObject.refreshTranslationsUI();
188
+ if (typeof this.translateActions?.setMessages === "function") {
189
+ this.translateActions.setMessages(this.translates);
266
190
  }
267
191
  }
268
192
 
269
193
  syncCachedMessagesToStore() {
270
- if (typeof this.translateActions?.setMessages !== "function") {
271
- return;
194
+ if (typeof this.translateActions?.setMessages === "function") {
195
+ this.translateActions.setMessages(this.translates);
272
196
  }
273
-
274
- this.translateActions.setMessages({
275
- ...this.translates,
276
- });
277
197
  }
278
198
 
279
199
  getPersistRequestToken(store, type, key) {
280
- return [
281
- this.language,
282
- this.normalizeId(this.defaultCompany?.id),
283
- String(store || "").trim(),
284
- String(type || "").trim(),
285
- String(key || "").trim(),
286
- ].join("::");
287
- }
288
-
289
- resolveReferenceId(value) {
290
- if (!value) return null;
291
-
292
- if (typeof value === "object") {
293
- return this.normalizeId(value.id || value["@id"] || value.people || value.language);
294
- }
295
-
296
- return this.normalizeId(value);
200
+ return `${this.language}:${this.normalizeId(this.defaultCompany?.id)}:${store}:${type}:${key}`;
297
201
  }
298
202
 
299
203
  cacheTranslateRecord(record, fallbackCompanyId = null, fallbackLanguage = this.language) {
300
- const companyId = this.resolveReferenceId(record?.people) || this.normalizeId(fallbackCompanyId);
204
+ const companyId =
205
+ this.resolveReferenceId(record?.people) || this.normalizeId(fallbackCompanyId);
301
206
  const language =
302
- this.normalizeLanguageCode(record?.language?.language || record?.language?.locale || fallbackLanguage) ||
303
- this.language;
207
+ this.normalizeLanguageCode(
208
+ record?.language?.language || record?.language?.locale || fallbackLanguage,
209
+ ) || this.language;
304
210
  const store = String(record?.store || "").trim();
305
211
  const type = String(record?.type || "").trim();
306
212
  const key = String(record?.key || "").trim();
@@ -310,56 +216,53 @@ export default class Translate {
310
216
  return false;
311
217
  }
312
218
 
313
- const previousMessage = this.getStoreBucket(companyId, store, false, language)?.[type]?.[key];
219
+ const previousMessage = this.getStoreBucket(companyId, store, false, language)?.[
220
+ type
221
+ ]?.[key];
314
222
  this.findMessage(store, type, key, message, companyId, language);
315
223
 
316
224
  return previousMessage !== message;
317
225
  }
318
-
319
- getMessageFromBuckets(store, type, key) {
320
- const companyIds = [
321
- this.currentCompany?.id,
322
- this.defaultCompany?.id,
323
- ]
324
- .map((value) => this.normalizeId(value))
325
- .filter(Boolean);
326
-
327
- for (const companyId of companyIds) {
328
- const companyMessage =
329
- this.getStoreBucket(companyId, store)?.[type]?.[key];
330
-
331
- if (companyMessage) {
332
- return companyMessage;
333
- }
334
- }
335
-
336
- return this.getStoreBucket(null, store)?.[type]?.[key];
337
- }
338
-
226
+
227
+ getMessageFromBuckets(store, type, key) {
228
+ const companyIds = [this.currentCompany?.id, this.defaultCompany?.id]
229
+ .map((value) => this.normalizeId(value))
230
+ .filter(Boolean);
231
+
232
+ // Prefer current company, then main/default company, then language-global bucket.
233
+ // Empty/whitespace values do not count as a hit so fallback can still apply.
234
+ for (const companyId of companyIds) {
235
+ const companyMessage = this.getStoreBucket(companyId, store)?.[type]?.[key];
236
+ if (isNonEmptyMessage(companyMessage)) {
237
+ return companyMessage;
238
+ }
239
+ }
240
+
241
+ const globalMessage = this.getStoreBucket(null, store)?.[type]?.[key];
242
+ if (isNonEmptyMessage(globalMessage)) {
243
+ return globalMessage;
244
+ }
245
+
246
+ return globalMessage;
247
+ }
248
+
339
249
  persistMissingTranslate(store, type, key, translate) {
340
250
  if (!store || !type || !key || !this.defaultCompany?.id) return;
341
251
 
342
- // verifica se tenho acesso ao defaultCompany
343
252
  const defaultCompanyId = this.normalizeId(this.defaultCompany?.id);
344
- if (
345
- !Array.isArray(this.companies) ||
346
- !this.companies.some((company) => this.normalizeId(company?.id) === defaultCompanyId)
347
- )
348
- return;
349
-
350
- if (this.hasPendingTranslate(store, type, key)) {
253
+ // Queueing is a local pending state used to drive resolve/discovery.
254
+ // Do NOT require defaultCompany to appear in this.companies: non-superadmin
255
+ // tenants often lack membership on the main company, but still must be able
256
+ // to resolve and cache the main catalog (read path). Backend gates writes.
257
+ if (!defaultCompanyId) {
351
258
  return;
352
259
  }
353
260
 
354
- const requestToken = this.getPersistRequestToken(store, type, key);
355
- if (this.pendingPersistRequests.has(requestToken)) {
261
+ if (this.hasPendingTranslate(store, type, key)) {
356
262
  return;
357
263
  }
358
264
 
359
- if (
360
- typeof this.translateActions?.queueMissingTranslate !== "function" ||
361
- typeof this.translateActions?.save !== "function"
362
- ) {
265
+ if (typeof this.translateActions?.queueMissingTranslate !== "function") {
363
266
  return;
364
267
  }
365
268
 
@@ -372,6 +275,19 @@ export default class Translate {
372
275
  translate,
373
276
  });
374
277
 
278
+ if (typeof this.translateActions?.resolveQueuedMessages === "function") {
279
+ return this.scheduleQueuedTranslateResolution();
280
+ }
281
+
282
+ const requestToken = this.getPersistRequestToken(store, type, key);
283
+ if (this.pendingPersistRequests.has(requestToken)) {
284
+ return this.pendingPersistRequests.get(requestToken);
285
+ }
286
+
287
+ if (typeof this.translateActions?.save !== "function") {
288
+ return;
289
+ }
290
+
375
291
  const request = Promise.resolve(
376
292
  this.translateActions.save({
377
293
  people: "/people/" + defaultCompanyId,
@@ -381,10 +297,14 @@ export default class Translate {
381
297
  key,
382
298
  translate,
383
299
  revised: false,
384
- })
300
+ }),
385
301
  )
386
302
  .then((result) => {
387
- const changed = this.cacheTranslateRecord(result, defaultCompanyId, this.language);
303
+ const changed = this.cacheTranslateRecord(
304
+ result,
305
+ defaultCompanyId,
306
+ this.language,
307
+ );
388
308
  this.removePendingTranslate(store, type, key);
389
309
  this.persist();
390
310
  if (changed) {
@@ -398,223 +318,182 @@ export default class Translate {
398
318
  });
399
319
 
400
320
  this.pendingPersistRequests.set(requestToken, request);
321
+ return request;
401
322
  }
402
323
 
403
324
  removePendingTranslate(store, type, key) {
404
- if (!store || !type || !key || !this.defaultCompany?.id) return;
405
-
406
325
  if (typeof this.translateActions?.removePendingTranslate !== "function") {
407
326
  return;
408
327
  }
409
328
 
410
329
  this.translateActions.removePendingTranslate({
411
330
  language: this.language,
412
- companyId: this.defaultCompany.id,
331
+ companyId: this.defaultCompany?.id,
413
332
  store,
414
333
  type,
415
334
  key,
416
335
  });
417
336
  }
418
-
419
- t(store, type, key) {
420
- const translate = this.getMessageFromBuckets(store, type, key);
421
- const fallbackTranslate = this.formatMessage(key);
422
- const shouldDiscoverStore =
423
- this.canDiscoverStore() && !this.hasDiscoveredStore(store);
424
-
425
- if (shouldDiscoverStore) {
426
- this.ensureStoreDiscovered(store)
427
- .then(() => {
428
- const discoveredTranslate = this.getMessageFromBuckets(store, type, key);
429
-
430
- if (discoveredTranslate) {
431
- return;
432
- }
433
337
 
434
- this.persistMissingTranslate(store, type, key, fallbackTranslate);
435
- })
436
- .catch(() => {});
437
- }
438
-
439
- if (!translate) {
440
- if (!shouldDiscoverStore) {
441
- this.persistMissingTranslate(store, type, key, fallbackTranslate);
442
- }
443
-
444
- return fallbackTranslate;
445
- }
446
-
447
- return translate;
448
- }
449
-
450
- reload() {
451
- this.clear();
452
- return this.discoveryAll({ force: true });
338
+ getQueuedTranslateGroups() {
339
+ return readQueuedGroups(
340
+ this.translateStore,
341
+ this.defaultCompany?.id,
342
+ this.language,
343
+ );
453
344
  }
454
345
 
455
- clear() {
456
- this.translates = {};
457
- this.discoveredStores.clear();
458
- this.pendingStoreDiscoveries.clear();
459
- this.pendingPersistRequests.clear();
460
- if (typeof this.translateActions?.setPendingMessages === "function") {
461
- this.translateActions.setPendingMessages({});
462
- }
463
- this.persist();
346
+ getQueuedTranslateGroupsForStore(store) {
347
+ return readQueuedGroupsForStore(
348
+ this.translateStore,
349
+ this.defaultCompany?.id,
350
+ store,
351
+ this.language,
352
+ );
464
353
  }
465
354
 
466
- shouldFetchStoreForCompany(store, company, force = false) {
467
- if (force) return true;
355
+ scheduleQueuedTranslateResolution() {
356
+ if (this.pendingTranslateResolutionPromise) {
357
+ return this.pendingTranslateResolutionPromise;
358
+ }
468
359
 
469
- const companyId = this.normalizeId(company?.id);
470
- if (!companyId || !store) return false;
360
+ this.pendingTranslateResolutionPromise = new Promise((resolve) => {
361
+ setTimeout(resolve, 0);
362
+ })
363
+ .then(() => this.resolveQueuedTranslations())
364
+ .catch(() => this.translates)
365
+ .finally(() => {
366
+ this.pendingTranslateResolutionPromise = null;
367
+ });
471
368
 
472
- const storeBucket = this.getStoreBucket(companyId, store, false, this.language);
473
- return storeBucket == null;
369
+ return this.pendingTranslateResolutionPromise;
474
370
  }
475
371
 
476
- async discoveryAll(options = {}) {
477
- const companies = this.getCompaniesToCache();
478
-
479
- for (const company of companies) {
480
- for (const store of this.getStoreList()) {
481
- await this.discoveryStoreTranslate(store, {
482
- ...options,
483
- companies: [company],
484
- });
485
- }
372
+ async resolveQueuedTranslations() {
373
+ if (typeof this.translateActions?.resolveQueuedMessages !== "function") {
374
+ return this.translates;
486
375
  }
487
376
 
488
- return this.translates;
489
- }
490
-
491
- async discoveryStoreTranslate(store, options = {}) {
492
- if (!store) return this.translates;
493
-
494
- const force = options.force === true;
495
- if (!force && this.hasDiscoveredStore(store)) {
377
+ const requests = this.getQueuedTranslateGroups();
378
+ if (requests.length === 0) {
496
379
  return this.translates;
497
380
  }
498
381
 
499
- const companies = Array.isArray(options.companies) && options.companies.length > 0
500
- ? options.companies
501
- : this.getCompaniesToCache();
382
+ // Resolve against every company we keep in cache (current + main) so fallback
383
+ // translations from the main company are available at runtime.
384
+ const companies = this.getCompaniesToCache();
502
385
  if (companies.length === 0) {
503
- this.markStoreDiscovered(store);
504
386
  return this.translates;
505
387
  }
506
388
 
507
- let hasChanges = false;
389
+ let changed = false;
390
+ const allResolvedItems = [];
391
+
508
392
  for (const company of companies) {
509
- const changed = await this.fetchTranslates(store, company, { force });
510
- hasChanges = changed || hasChanges;
511
- }
393
+ const companyId = this.normalizeId(company?.id);
394
+ if (!companyId) continue;
395
+
396
+ const response = await this.translateActions.resolveQueuedMessages({
397
+ people: "/people/" + companyId,
398
+ language: this.language,
399
+ requests,
400
+ });
512
401
 
513
- if (this.hasCachedBootstrapStore(store)) {
514
- this.markStoreDiscovered(store);
402
+ const resolvedItems = Array.isArray(response)
403
+ ? response
404
+ : response?.member || response?.["hydra:member"] || [];
405
+
406
+ resolvedItems.forEach((item) => {
407
+ changed =
408
+ this.cacheTranslateRecord(item, companyId, this.language) || changed;
409
+ this.removePendingTranslate(item.store, item.type, item.key);
410
+ allResolvedItems.push(item);
411
+ });
515
412
  }
516
413
 
517
- if (hasChanges) {
414
+ if (changed) {
518
415
  this.persist();
519
416
  this.notifyTranslationsUpdated();
520
417
  }
521
418
 
522
- return this.translates;
419
+ return allResolvedItems;
523
420
  }
524
421
 
525
- async fetchTranslates(store, company, options = {}) {
526
- if (!company?.id) return false;
422
+ t(store, type, key) {
423
+ const translate = this.getMessageFromBuckets(store, type, key);
424
+ const fallbackTranslate = this.formatMessage(key);
527
425
 
528
- const force = options.force === true;
529
- if (!this.shouldFetchStoreForCompany(store, company, force)) {
530
- return false;
531
- }
426
+ if (!translate) {
427
+ const scheduleKey = `${store}:${type}:${key}`;
428
+ if (!this.pendingMissingTranslateSchedules.has(scheduleKey)) {
429
+ this.pendingMissingTranslateSchedules.add(scheduleKey);
430
+ setTimeout(() => {
431
+ this.pendingMissingTranslateSchedules.delete(scheduleKey);
432
+ this.persistMissingTranslate(store, type, key, fallbackTranslate);
433
+ }, 0);
434
+ }
532
435
 
533
- const params = {
534
- store: store,
535
- "language.language": this.language,
536
- people: "/people/" + company.id,
537
- };
538
-
539
- const storeTranslates = [];
540
- let page = 1;
541
- let firstPageSize = null;
542
- const maxPages = 1000;
543
-
544
- while (page <= maxPages) {
545
- const pageItems = await this.translateActions.getItems({
546
- ...params,
547
- page,
548
- });
549
-
550
- if (!Array.isArray(pageItems) || pageItems.length === 0) break;
551
-
552
- storeTranslates.push(...pageItems);
553
- if (firstPageSize == null) firstPageSize = pageItems.length;
554
- if (pageItems.length < firstPageSize) break;
555
-
556
- page += 1;
436
+ this.ensureStoreDiscovered(store);
437
+ return fallbackTranslate;
557
438
  }
558
439
 
559
- const companyId = this.normalizeId(company?.id);
560
- const storeBucket = this.getStoreBucket(companyId, store, true);
561
- let changed = false;
440
+ return translate;
441
+ }
562
442
 
563
- if (storeBucket) {
564
- if (Object.keys(storeBucket).length > 0) {
565
- changed = true;
566
- }
567
- Object.keys(storeBucket).forEach((type) => {
568
- delete storeBucket[type];
569
- });
570
- }
443
+ reload() {
444
+ this.translates = loadStorageObject(TRANSLATES_STORAGE_KEY);
445
+ this.syncCachedMessagesToStore();
446
+ }
571
447
 
572
- storeTranslates.forEach((element) => {
573
- changed = this.cacheTranslateRecord(
574
- {
575
- ...element,
576
- people: element.people || companyId,
577
- },
578
- companyId,
579
- this.language,
580
- ) || changed;
581
- this.removePendingTranslate(element.store, element.type, element.key);
582
- });
448
+ clear() {
449
+ this.translates = {};
450
+ this.discoveredStores.clear();
451
+ this.pendingStoreDiscoveries.clear();
452
+ this.persist();
453
+ }
454
+
455
+ shouldFetchStoreForCompany(store, company, force = false) {
456
+ if (force) return true;
457
+ if (!company?.id || !store) return false;
458
+ return this.getStoreBucket(company.id, store) == null;
459
+ }
460
+
461
+ async discoveryAll(options = {}) {
462
+ return runDiscoveryAll(this, options);
463
+ }
583
464
 
584
- return changed || storeTranslates.length === 0;
465
+ async discoveryStoreTranslate(store, options = {}) {
466
+ return runDiscoveryStoreTranslate(this, store, options);
467
+ }
468
+
469
+ async fetchTranslates(store, company, options = {}) {
470
+ return runFetchTranslates(this, store, company, options);
471
+ }
472
+
473
+ async resolveStoreTranslates(store, company, options = {}) {
474
+ return runResolveStoreTranslates(this, store, company, options);
585
475
  }
586
476
 
587
477
  persist() {
588
478
  if (typeof localStorage !== "undefined") {
589
479
  localStorage.setItem("translates", JSON.stringify(this.translates));
590
480
  }
591
-
592
481
  this.syncCachedMessagesToStore();
593
482
  }
594
483
 
595
484
  findMessage(store, type, key, message, companyId = null, language = this.language) {
596
- const storeBucket = this.getStoreBucket(companyId, store, true, language);
597
- if (!storeBucket) {
485
+ const written = writeMessageToBucket(
486
+ this.translates,
487
+ store,
488
+ type,
489
+ key,
490
+ message,
491
+ companyId,
492
+ language,
493
+ );
494
+ if (written == null) {
598
495
  return this.formatMessage(key);
599
496
  }
600
-
601
- if (!storeBucket[type]) {
602
- storeBucket[type] = {};
603
- }
604
-
605
- if (message !== null)
606
- storeBucket[type][key] = message;
607
-
608
- return storeBucket[type][key] || this.formatMessage(key);
609
- }
610
-
611
- formatMessage(key) {
612
- if (!key) return "";
613
-
614
- return key
615
- .replace(/([a-z])([A-Z])/g, "$1_$2")
616
- .replace(/_/g, " ")
617
- .replace(/-/g, " ")
618
- .replace(/^\w/, (c) => c.toUpperCase());
619
- }
620
- }
497
+ return written || this.formatMessage(key);
498
+ }
499
+ }