@controleonline/ui-common 1.2.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/.github/agents/developer.agent.md +30 -0
  2. package/.github/agents/devops.agent.md +30 -0
  3. package/.github/agents/qa.agent.md +30 -0
  4. package/.github/agents/security.agent.md +30 -0
  5. package/.github/workflows/npmjs.yml +35 -0
  6. package/.github/workflows/pull-request-checks.yml +19 -0
  7. package/.scrutinizer.yml +24 -0
  8. package/FUNDING.yml +1 -0
  9. package/package.json +28 -0
  10. package/src/api/fetch.js +74 -0
  11. package/src/api/index.js +155 -0
  12. package/src/api/localDB.js +131 -0
  13. package/src/api/queue.js +66 -0
  14. package/src/react/assets/tap.mp3 +0 -0
  15. package/src/react/components/AddImportModal.js +157 -0
  16. package/src/react/components/AddImportModal.styles.js +81 -0
  17. package/src/react/components/BackgroundRuntimeBridge.js +219 -0
  18. package/src/react/components/BottomNavigationBar.config.js +238 -0
  19. package/src/react/components/BottomNavigationBar.js +120 -0
  20. package/src/react/components/BottomNavigationBar.styles.js +110 -0
  21. package/src/react/components/CategoryForm.js +572 -0
  22. package/src/react/components/CategoryForm.styles.js +334 -0
  23. package/src/react/components/ConfirmModal.js +24 -0
  24. package/src/react/components/ConfirmModal.styles.js +43 -0
  25. package/src/react/components/ContextHelpButton.js +69 -0
  26. package/src/react/components/ContextHelpButton.styles.js +62 -0
  27. package/src/react/components/DefaultProvider.native.js +848 -0
  28. package/src/react/components/DefaultProvider.styles.js +21 -0
  29. package/src/react/components/DefaultProvider.web.js +909 -0
  30. package/src/react/components/DeliveryPushBridge.native.js +314 -0
  31. package/src/react/components/DeliveryPushBridge.web.js +5 -0
  32. package/src/react/components/DeviceAlertSoundService.js +499 -0
  33. package/src/react/components/EntityLogContent.js +909 -0
  34. package/src/react/components/EntityLogContent.styles.js +400 -0
  35. package/src/react/components/EventBus.js +12 -0
  36. package/src/react/components/KioskModeBridge.js +65 -0
  37. package/src/react/components/LauncherModeBridge.js +47 -0
  38. package/src/react/components/LinkedOrderProductsTab.js +388 -0
  39. package/src/react/components/LinkedOrderProductsTab.styles.js +180 -0
  40. package/src/react/components/ManagerPushBridge.native.js +345 -0
  41. package/src/react/components/MessageService.js +314 -0
  42. package/src/react/components/Paywall/Google.js +89 -0
  43. package/src/react/components/PrintService.js +778 -0
  44. package/src/react/components/ProductCatalogCacheService.js +45 -0
  45. package/src/react/components/RemoteCheckoutService.js +292 -0
  46. package/src/react/components/RuntimeBottomNavigationBar.js +151 -0
  47. package/src/react/components/RuntimeInfoFooter.js +214 -0
  48. package/src/react/components/RuntimeInfoFooter.styles.js +56 -0
  49. package/src/react/components/SystemErrorToast.js +29 -0
  50. package/src/react/components/SystemErrorToast.styles.js +38 -0
  51. package/src/react/components/TouchFeedbackProvider.js +65 -0
  52. package/src/react/components/UnifiedPaymentBar.js +155 -0
  53. package/src/react/components/UnifiedPaymentBar.styles.js +174 -0
  54. package/src/react/components/WebsocketListener.native.js +487 -0
  55. package/src/react/components/WebsocketListener.web.js +102 -0
  56. package/src/react/components/inputs/IdInput.js +49 -0
  57. package/src/react/components/inputs/IdInput.styles.js +26 -0
  58. package/src/react/components/inputs/InvoiceDueDateFilterInput.js +74 -0
  59. package/src/react/components/inputs/InvoiceDueDateFilterInput.styles.js +34 -0
  60. package/src/react/components/inputs/InvoiceDueDateRangeFilterInput.js +115 -0
  61. package/src/react/components/inputs/InvoiceDueDateRangeFilterInput.styles.js +39 -0
  62. package/src/react/components/lists/CategoriesList.js +56 -0
  63. package/src/react/components/lists/PaymentTypeList.js +55 -0
  64. package/src/react/components/lists/ReceiverList.js +56 -0
  65. package/src/react/components/lists/SelectList.styles.js +18 -0
  66. package/src/react/components/lists/StatusList.js +55 -0
  67. package/src/react/components/lists/WalletList.js +54 -0
  68. package/src/react/components/radio/Spotify.js +144 -0
  69. package/src/react/components/radio/Spotify.styles.js +32 -0
  70. package/src/react/components/radio/YouTube.js +82 -0
  71. package/src/react/components/radio/YouTube.styles.js +16 -0
  72. package/src/react/components/toastConfig.js +16 -0
  73. package/src/react/config/deviceConfigBootstrap.js +571 -0
  74. package/src/react/index.js +4 -0
  75. package/src/react/localStorage.js +30 -0
  76. package/src/react/pages/EntityLogPage.js +124 -0
  77. package/src/react/pages/EntityLogPage.styles.js +17 -0
  78. package/src/react/pages/GenericLogPage.js +760 -0
  79. package/src/react/pages/GenericLogPage.styles.js +401 -0
  80. package/src/react/pages/Imports.js +361 -0
  81. package/src/react/pages/Imports.styles.js +223 -0
  82. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +462 -0
  83. package/src/react/pages/SettingsPage/index.js +1171 -0
  84. package/src/react/pages/SettingsPage/index.styles.js +122 -0
  85. package/src/react/print/jobs.js +147 -0
  86. package/src/react/print/providers/local.js +53 -0
  87. package/src/react/print/providers/remote.js +81 -0
  88. package/src/react/print/selection.js +296 -0
  89. package/src/react/print/usePrintButtonController.js +474 -0
  90. package/src/react/router/routes.js +42 -0
  91. package/src/react/services/NetworkPrinterService.native.js +391 -0
  92. package/src/react/services/NetworkPrinterService.web.js +15 -0
  93. package/src/react/stores/index.js +128 -0
  94. package/src/react/stores/storeErrorBridge.js +36 -0
  95. package/src/react/utils/backgroundRuntimeRegistration.js +69 -0
  96. package/src/react/utils/cashPayment.js +87 -0
  97. package/src/react/utils/commercialDocumentOrders.js +269 -0
  98. package/src/react/utils/dateRangeFilter.js +264 -0
  99. package/src/react/utils/deviceRuntime.js +193 -0
  100. package/src/react/utils/entityDisplay.js +231 -0
  101. package/src/react/utils/entityLog.js +649 -0
  102. package/src/react/utils/fileUrl.js +102 -0
  103. package/src/react/utils/frontendDebugLog.js +45 -0
  104. package/src/react/utils/googleMapsConfig.js +49 -0
  105. package/src/react/utils/importStatus.js +100 -0
  106. package/src/react/utils/invoiceDueDateMessages.js +14 -0
  107. package/src/react/utils/invoicePresentation.js +46 -0
  108. package/src/react/utils/logSettings.js +143 -0
  109. package/src/react/utils/maintenanceSettings.js +53 -0
  110. package/src/react/utils/managerOrderNotifications.js +523 -0
  111. package/src/react/utils/mapNavigation.js +76 -0
  112. package/src/react/utils/menuCatalogConfig.js +8 -0
  113. package/src/react/utils/menuCatalogDownload.js +157 -0
  114. package/src/react/utils/networkCameraDevices.js +175 -0
  115. package/src/react/utils/networkDeviceProfiles.js +135 -0
  116. package/src/react/utils/normalizedCatalogDownload.js +157 -0
  117. package/src/react/utils/notificationNavigation.js +94 -0
  118. package/src/react/utils/notificationSound.js +42 -0
  119. package/src/react/utils/paymentDevices.js +397 -0
  120. package/src/react/utils/paymentGatewayExecution.js +91 -0
  121. package/src/react/utils/paymentOptions.js +88 -0
  122. package/src/react/utils/peopleDisplay.js +39 -0
  123. package/src/react/utils/printerDevices.js +465 -0
  124. package/src/react/utils/remotePayment.js +61 -0
  125. package/src/react/utils/runtimeFooter.js +475 -0
  126. package/src/react/utils/runtimeLanguage.js +50 -0
  127. package/src/react/utils/runtimeMenu.js +224 -0
  128. package/src/react/utils/screenMetrics.js +30 -0
  129. package/src/react/utils/shopConfig.js +294 -0
  130. package/src/react/utils/shopFranchises.js +114 -0
  131. package/src/react/utils/socketRuntimePipeline.js +454 -0
  132. package/src/react/utils/spoolAckTracker.js +77 -0
  133. package/src/react/utils/storeColumns.js +427 -0
  134. package/src/react/utils/systemErrorChannel.js +109 -0
  135. package/src/react/utils/systemErrorMessage.js +67 -0
  136. package/src/react/utils/toastPresentation.js +53 -0
  137. package/src/react/utils/websocketSharedRuntime.js +826 -0
  138. package/src/store/acl/getters.js +4 -0
  139. package/src/store/acl/index.js +19 -0
  140. package/src/store/acl/mutation_types.js +4 -0
  141. package/src/store/acl/mutations.js +17 -0
  142. package/src/store/address/city.js +22 -0
  143. package/src/store/categories/category_file/index.js +25 -0
  144. package/src/store/categories/index.js +93 -0
  145. package/src/store/configs/customActions.js +131 -0
  146. package/src/store/configs/index.js +25 -0
  147. package/src/store/connections/customActions.js +26 -0
  148. package/src/store/connections/index.js +124 -0
  149. package/src/store/device/index.js +22 -0
  150. package/src/store/device_config/customActions.js +190 -0
  151. package/src/store/device_config/index.js +28 -0
  152. package/src/store/entity_log/customActions.js +63 -0
  153. package/src/store/entity_log/index.js +25 -0
  154. package/src/store/extra/data.js +85 -0
  155. package/src/store/extra/fields.js +88 -0
  156. package/src/store/file/actions.js +27 -0
  157. package/src/store/file/index.js +24 -0
  158. package/src/store/imports/index.js +88 -0
  159. package/src/store/print/customActions.js +251 -0
  160. package/src/store/print/index.js +28 -0
  161. package/src/store/printer/customActions.js +54 -0
  162. package/src/store/printer/index.js +26 -0
  163. package/src/store/runtime_debug/customActions.js +71 -0
  164. package/src/store/runtime_debug/index.js +30 -0
  165. package/src/store/status/index.js +65 -0
  166. package/src/store/timezones/index.js +43 -0
  167. package/src/store/websocket/customActions.js +24 -0
  168. package/src/store/websocket/index.js +28 -0
  169. package/src/tests/react/components/LauncherModeBridge.test.js +93 -0
  170. package/src/tests/react/config/deviceConfigBootstrap.test.js +337 -0
  171. package/src/tests/react/print/localPrintProvider.test.js +45 -0
  172. package/src/tests/react/utils/backgroundRuntimeRegistration.test.js +123 -0
  173. package/src/tests/react/utils/cashPayment.test.js +41 -0
  174. package/src/tests/react/utils/dateRangeFilter.test.mjs +29 -0
  175. package/src/tests/react/utils/entityDisplay.test.js +18 -0
  176. package/src/tests/react/utils/googleMapsConfig.test.js +32 -0
  177. package/src/tests/react/utils/importStatus.test.js +85 -0
  178. package/src/tests/react/utils/invoicePresentation.test.mjs +40 -0
  179. package/src/tests/react/utils/managerOrderNotifications.test.js +127 -0
  180. package/src/tests/react/utils/mapNavigation.test.js +58 -0
  181. package/src/tests/react/utils/notificationNavigation.test.js +39 -0
  182. package/src/tests/react/utils/notificationSound.test.js +27 -0
  183. package/src/tests/react/utils/paymentDevices.test.js +88 -0
  184. package/src/tests/react/utils/runtimeFooter.test.js +429 -0
  185. package/src/tests/react/utils/runtimeLanguage.test.js +80 -0
  186. package/src/tests/react/utils/runtimeMenu.test.js +174 -0
  187. package/src/tests/react/utils/shopFranchises.test.js +82 -0
  188. package/src/tests/react/utils/socketRuntimePipeline.test.js +124 -0
  189. package/src/tests/react/utils/spoolAckTracker.test.js +40 -0
  190. package/src/tests/react/utils/storeErrorBridge.test.js +66 -0
  191. package/src/tests/react/utils/systemErrorChannel.test.js +58 -0
  192. package/src/tests/react/utils/systemErrorMessage.test.js +59 -0
  193. package/src/tests/react/utils/toastPresentation.test.js +75 -0
  194. package/src/tests/react/utils/translate.test.mjs +396 -0
  195. package/src/tests/react/utils/websocketSharedRuntime.test.js +245 -0
  196. package/src/utils/apiEntryPoint.js +31 -0
  197. package/src/utils/appDomain.js +50 -0
  198. package/src/utils/config.js +14 -0
  199. package/src/utils/formatter.js +419 -0
  200. package/src/utils/integrationConfigs.js +79 -0
  201. package/src/utils/methods.js +23 -0
  202. package/src/utils/oauth.js +42 -0
  203. package/src/utils/translate.js +620 -0
@@ -0,0 +1,87 @@
1
+ import Formatter from '@controleonline/ui-common/src/utils/formatter';
2
+ import {isCashPaymentOption} from './paymentOptions';
3
+
4
+ export const parseMoneyInputValue = value => {
5
+ const numericValue = String(value || '').replace(/\D/g, '');
6
+ if (!numericValue) {
7
+ return 0;
8
+ }
9
+
10
+ return Number(numericValue) / 100;
11
+ };
12
+
13
+ export const formatMoneyInputValue = value => {
14
+ const normalizedValue = Number(value || 0);
15
+ if (normalizedValue <= 0) {
16
+ return '';
17
+ }
18
+
19
+ return Formatter.formatMoney(normalizedValue);
20
+ };
21
+
22
+ export const normalizeMoneyInputText = text => {
23
+ const numericValue = String(text || '').replace(/\D/g, '');
24
+ if (!numericValue) {
25
+ return '';
26
+ }
27
+
28
+ return Formatter.formatMoney(Number(numericValue) / 100);
29
+ };
30
+
31
+ export const resolveCashPaymentDetails = ({
32
+ allowPartial = false,
33
+ receivedAmount = 0,
34
+ totalAmount = 0,
35
+ }) => {
36
+ const normalizedReceivedAmount = Math.max(Number(receivedAmount || 0), 0);
37
+ const normalizedTotalAmount = Math.max(Number(totalAmount || 0), 0);
38
+ const appliedAmount = allowPartial
39
+ ? Math.min(normalizedReceivedAmount, normalizedTotalAmount)
40
+ : normalizedTotalAmount;
41
+ const changeAmount = Math.max(normalizedReceivedAmount - appliedAmount, 0);
42
+ const missingAmount = Math.max(normalizedTotalAmount - normalizedReceivedAmount, 0);
43
+
44
+ return {
45
+ appliedAmount,
46
+ changeAmount,
47
+ missingAmount,
48
+ needsChange: changeAmount > 0.009,
49
+ receivedAmount: normalizedReceivedAmount,
50
+ };
51
+ };
52
+
53
+ const GATEWAY_BYPASS_PAYMENT_CODES = ['DINHEIRO', 'VOUCHER_CORTESIA'];
54
+
55
+ const normalizePaymentCode = value =>
56
+ String(value || '')
57
+ .trim()
58
+ .toUpperCase();
59
+
60
+ export const hasIntegratedPaymentCode = payment =>
61
+ normalizePaymentCode(payment?.paymentCode) !== '';
62
+
63
+ export const shouldBypassGatewayForPayment = payment =>
64
+ GATEWAY_BYPASS_PAYMENT_CODES.includes(
65
+ normalizePaymentCode(payment?.paymentCode),
66
+ );
67
+
68
+ export const isGatewayFreePayment = payment =>
69
+ !!payment?.wallet &&
70
+ !!payment?.paymentType &&
71
+ (!hasIntegratedPaymentCode(payment) || shouldBypassGatewayForPayment(payment));
72
+
73
+ export const isCashPaymentWithoutGateway = payment =>
74
+ isCashPaymentOption(payment) && isGatewayFreePayment(payment);
75
+
76
+ export const createInvoiceForGatewayFreePayment = async ({
77
+ payment,
78
+ total = 0,
79
+ createInvoice,
80
+ }) => {
81
+ if (!isGatewayFreePayment(payment) || typeof createInvoice !== 'function') {
82
+ return false;
83
+ }
84
+
85
+ await createInvoice(payment, Number(total || 0));
86
+ return true;
87
+ };
@@ -0,0 +1,269 @@
1
+ import { api } from '@controleonline/ui-common/src/api';
2
+
3
+ const normalizeStatusKey = value => String(value || '').trim().toLowerCase();
4
+
5
+ export const normalizeEntityId = value => {
6
+ if (!value) {
7
+ return '';
8
+ }
9
+
10
+ if (typeof value === 'object') {
11
+ return normalizeEntityId(value?.['@id'] || value?.id);
12
+ }
13
+
14
+ return String(value).replace(/\D/g, '');
15
+ };
16
+
17
+ export const toEntityIri = (value, resource) => {
18
+ if (!value) {
19
+ return null;
20
+ }
21
+
22
+ if (typeof value === 'object') {
23
+ if (value?.['@id']) {
24
+ return value['@id'];
25
+ }
26
+
27
+ const nestedId = normalizeEntityId(value?.id);
28
+ return nestedId ? `/${resource}/${nestedId}` : null;
29
+ }
30
+
31
+ const normalizedValue = String(value).trim();
32
+ if (normalizedValue.startsWith('/')) {
33
+ return normalizedValue;
34
+ }
35
+
36
+ const normalizedId = normalizeEntityId(normalizedValue);
37
+ return normalizedId ? `/${resource}/${normalizedId}` : null;
38
+ };
39
+
40
+ export const extractCollectionItems = response => {
41
+ if (Array.isArray(response)) return response;
42
+ if (Array.isArray(response?.member)) return response.member;
43
+ if (Array.isArray(response?.['hydra:member'])) return response['hydra:member'];
44
+ return [];
45
+ };
46
+
47
+ const buildStatusIriFromId = value => {
48
+ const normalizedId = normalizeEntityId(value);
49
+ return normalizedId ? `/statuses/${normalizedId}` : null;
50
+ };
51
+
52
+ let openOrderStatusIriCache = null;
53
+
54
+ export const resolveOpenOrderStatusIri = async fallbackStatusId => {
55
+ if (openOrderStatusIriCache) {
56
+ return openOrderStatusIriCache;
57
+ }
58
+
59
+ const fallbackIri = buildStatusIriFromId(fallbackStatusId);
60
+
61
+ try {
62
+ const response = await api.fetch('statuses', {
63
+ params: {
64
+ context: 'order',
65
+ realStatus: 'open',
66
+ status: 'open',
67
+ },
68
+ });
69
+ const items = extractCollectionItems(response);
70
+ const matchedStatus =
71
+ items.find(
72
+ item =>
73
+ normalizeStatusKey(item?.realStatus) === 'open' &&
74
+ normalizeStatusKey(item?.status) === 'open',
75
+ ) || items[0];
76
+ const resolvedIri =
77
+ matchedStatus?.['@id'] ||
78
+ buildStatusIriFromId(matchedStatus?.id) ||
79
+ fallbackIri;
80
+
81
+ if (resolvedIri) {
82
+ openOrderStatusIriCache = resolvedIri;
83
+ }
84
+
85
+ return resolvedIri;
86
+ } catch (error) {
87
+ return fallbackIri;
88
+ }
89
+ };
90
+
91
+ export const isOpenCommercialOrder = order =>
92
+ normalizeStatusKey(order?.status?.realStatus) === 'open' &&
93
+ normalizeStatusKey(order?.status?.status) === 'open';
94
+
95
+ export const fetchLinkedOrder = async contractRef => {
96
+ if (!contractRef) {
97
+ return null;
98
+ }
99
+
100
+ const response = await api.fetch('orders', {
101
+ params: {
102
+ contract: toEntityIri(contractRef, 'contracts'),
103
+ 'order[alterDate]': 'DESC',
104
+ },
105
+ });
106
+
107
+ return extractCollectionItems(response)[0] || null;
108
+ };
109
+
110
+ export const fetchOrderProducts = async orderRef => {
111
+ if (!orderRef) {
112
+ return [];
113
+ }
114
+
115
+ const response = await api.fetch('order_products', {
116
+ params: {
117
+ order: toEntityIri(orderRef, 'orders'),
118
+ },
119
+ });
120
+
121
+ return extractCollectionItems(response);
122
+ };
123
+
124
+ export const searchCompanyProducts = async ({ companyId, query = '' }) => {
125
+ if (!companyId) {
126
+ return [];
127
+ }
128
+
129
+ const params = {
130
+ company: toEntityIri(companyId, 'people'),
131
+ active: 1,
132
+ 'order[product]': 'ASC',
133
+ };
134
+
135
+ if (String(query || '').trim()) {
136
+ params.product = String(query).trim();
137
+ }
138
+
139
+ const response = await api.fetch('products', { params });
140
+ return extractCollectionItems(response);
141
+ };
142
+
143
+ export const createLinkedOrder = async ({
144
+ contractRef,
145
+ provider,
146
+ client,
147
+ payer,
148
+ app = 'CRM',
149
+ orderType = 'sale',
150
+ fallbackStatusId = null,
151
+ }) => {
152
+ const status = await resolveOpenOrderStatusIri(fallbackStatusId);
153
+ const payload = {
154
+ app,
155
+ orderType,
156
+ contract: toEntityIri(contractRef, 'contracts'),
157
+ provider: toEntityIri(provider, 'people'),
158
+ };
159
+
160
+ if (status) {
161
+ payload.status = status;
162
+ }
163
+
164
+ const clientIri = toEntityIri(client, 'people');
165
+ if (clientIri) {
166
+ payload.client = clientIri;
167
+ }
168
+
169
+ const payerIri = toEntityIri(payer || client, 'people');
170
+ if (payerIri) {
171
+ payload.payer = payerIri;
172
+ }
173
+
174
+ return api.fetch('orders', {
175
+ method: 'POST',
176
+ body: payload,
177
+ });
178
+ };
179
+
180
+ export const ensureLinkedOrder = async ({
181
+ contractRef,
182
+ provider,
183
+ client,
184
+ payer,
185
+ app = 'CRM',
186
+ orderType = 'sale',
187
+ fallbackStatusId = null,
188
+ }) => {
189
+ const existingOrder = await fetchLinkedOrder(contractRef);
190
+ if (existingOrder) {
191
+ return existingOrder;
192
+ }
193
+
194
+ return createLinkedOrder({
195
+ contractRef,
196
+ provider,
197
+ client,
198
+ payer,
199
+ app,
200
+ orderType,
201
+ fallbackStatusId,
202
+ });
203
+ };
204
+
205
+ export const addProductsToOrder = async ({ orderId, products = [] }) => {
206
+ const normalizedOrderId = normalizeEntityId(orderId);
207
+ const normalizedProducts = products
208
+ .map(item => ({
209
+ product: normalizeEntityId(item?.product),
210
+ quantity: Number(item?.quantity || 1),
211
+ }))
212
+ .filter(item => item.product);
213
+
214
+ if (!normalizedOrderId || normalizedProducts.length === 0) {
215
+ return null;
216
+ }
217
+
218
+ return api.fetch(`orders/${normalizedOrderId}/add-products`, {
219
+ method: 'PUT',
220
+ body: normalizedProducts,
221
+ });
222
+ };
223
+
224
+ export const updateOrderProductQuantity = async ({ orderProductId, quantity }) => {
225
+ const normalizedId = normalizeEntityId(orderProductId);
226
+ if (!normalizedId) {
227
+ return null;
228
+ }
229
+
230
+ return api.fetch(`order_products/${normalizedId}`, {
231
+ method: 'PUT',
232
+ body: {
233
+ id: Number(normalizedId),
234
+ quantity: Number(quantity),
235
+ },
236
+ });
237
+ };
238
+
239
+ export const removeOrderProduct = async orderProductId => {
240
+ const normalizedId = normalizeEntityId(orderProductId);
241
+ if (!normalizedId) {
242
+ return null;
243
+ }
244
+
245
+ return api.fetch(`order_products/${normalizedId}`, {
246
+ method: 'DELETE',
247
+ });
248
+ };
249
+
250
+ export const fetchLatestProposalForClient = async ({ provider, client }) => {
251
+ const providerIri = toEntityIri(provider, 'people');
252
+ const clientIri = toEntityIri(client, 'people');
253
+
254
+ if (!providerIri || !clientIri) {
255
+ return null;
256
+ }
257
+
258
+ const response = await api.fetch('contracts', {
259
+ params: {
260
+ provider: providerIri,
261
+ client: clientIri,
262
+ 'contractModel.context': 'proposal',
263
+ 'order[alterDate]': 'DESC',
264
+ },
265
+ });
266
+
267
+ return extractCollectionItems(response)[0] || null;
268
+ };
269
+ // TODO(store-first): quando este arquivo for mexido, mover a leitura para stores, remover api.fetch e evitar repassar dados em objetos quando o store ja resolver isso.
@@ -0,0 +1,264 @@
1
+ const DATE_INPUT_PATTERN_YMD = /^\d{4}-\d{2}-\d{2}$/;
2
+ const DATE_INPUT_PATTERN_DMY = /^\d{2}\/\d{2}\/\d{4}$/;
3
+
4
+ const DATE_FILTER_LABEL_KEYS = {
5
+ all: 'period_all',
6
+ today: 'period_today',
7
+ yesterday: 'period_yesterday',
8
+ '7d': 'period_7d',
9
+ '30d': 'period_30d',
10
+ '90d': 'period_90d',
11
+ this_month: 'period_this_month',
12
+ last_month: 'period_last_month',
13
+ custom: 'period_custom',
14
+ };
15
+
16
+ const DATE_FILTER_OPTION_LABELS = {
17
+ '90d': '90 dias',
18
+ };
19
+
20
+ const pad2 = value => String(value).padStart(2, '0');
21
+
22
+ const formatDateToApi = date =>
23
+ `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`;
24
+
25
+ const resolveOrderLabel = key => global.t?.t('orders', 'label', key);
26
+ const resolveYesterdayLabel = () => global.t?.t('users', 'date', 'yesterday');
27
+
28
+ const createDayStart = date =>
29
+ new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
30
+
31
+ const createDayEnd = date =>
32
+ new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
33
+
34
+ const formatDateLabel = date => date.toLocaleDateString('pt-BR');
35
+ const buildNormalizedDate = (year, month, day) => {
36
+ const parsedDate = new Date(year, month - 1, day, 0, 0, 0, 0);
37
+
38
+ if (
39
+ Number.isNaN(parsedDate.getTime()) ||
40
+ parsedDate.getFullYear() !== year ||
41
+ parsedDate.getMonth() !== month - 1 ||
42
+ parsedDate.getDate() !== day
43
+ ) {
44
+ return null;
45
+ }
46
+
47
+ return parsedDate;
48
+ };
49
+
50
+ const createRangeEnd = (baseDate, useCurrentMoment = false) =>
51
+ useCurrentMoment ? new Date(baseDate) : createDayEnd(baseDate);
52
+
53
+ const resolveRelativeStart = (baseDate, days, relativeMode = 'calendar') => {
54
+ if (relativeMode === 'rolling') {
55
+ const startDate = new Date(baseDate);
56
+ startDate.setDate(startDate.getDate() - days);
57
+ return startDate;
58
+ }
59
+
60
+ const startDate = createDayStart(baseDate);
61
+ startDate.setDate(startDate.getDate() - (days - 1));
62
+ return startDate;
63
+ };
64
+
65
+ // Shared parser for screens that accept YYYY-MM-DD or DD/MM/YYYY input.
66
+ export const parseDateInput = value => {
67
+ const normalizedValue = String(value || '').trim();
68
+
69
+ if (DATE_INPUT_PATTERN_YMD.test(normalizedValue)) {
70
+ const [year, month, day] = normalizedValue.split('-').map(Number);
71
+ return buildNormalizedDate(year, month, day);
72
+ }
73
+
74
+ if (DATE_INPUT_PATTERN_DMY.test(normalizedValue)) {
75
+ const [day, month, year] = normalizedValue.split('/').map(Number);
76
+ return buildNormalizedDate(year, month, day);
77
+ }
78
+
79
+ return null;
80
+ };
81
+
82
+ export const normalizeDateInputToYmd = value => {
83
+ const parsedDate = parseDateInput(value);
84
+
85
+ if (!parsedDate) {
86
+ return null;
87
+ }
88
+
89
+ return `${parsedDate.getFullYear()}-${pad2(parsedDate.getMonth() + 1)}-${pad2(parsedDate.getDate())}`;
90
+ };
91
+
92
+ // Validation stays centralized so every screen uses the same rules and messages.
93
+ export const validateCustomDateRange = (fromInput, toInput) => {
94
+ const fromValue = String(fromInput || '').trim();
95
+ const toValue = String(toInput || '').trim();
96
+ const fromDate = fromValue ? parseDateInput(fromValue) : null;
97
+ const toDate = toValue ? parseDateInput(toValue) : null;
98
+
99
+ if ((fromValue && !fromDate) || (toValue && !toDate)) {
100
+ return global.t?.t('orders', 'validation', 'invalid_date_format');
101
+ }
102
+
103
+ if (fromDate && toDate && fromDate > toDate) {
104
+ return global.t?.t('orders', 'validation', 'invalid_date_range');
105
+ }
106
+
107
+ return '';
108
+ };
109
+
110
+ const resolveDateObjectsRange = (dateFilterKey, customRange = null, options = {}) => {
111
+ const {
112
+ baseDate = new Date(),
113
+ relativeMode = 'calendar',
114
+ useCurrentMoment = false,
115
+ } = options;
116
+ const now = new Date(baseDate);
117
+
118
+ if (dateFilterKey === 'today') {
119
+ return {
120
+ afterDate: createDayStart(now),
121
+ beforeDate: createRangeEnd(now, useCurrentMoment),
122
+ };
123
+ }
124
+
125
+ if (dateFilterKey === 'yesterday') {
126
+ const yesterday = new Date(now);
127
+ yesterday.setDate(yesterday.getDate() - 1);
128
+
129
+ return {
130
+ afterDate: createDayStart(yesterday),
131
+ beforeDate: createDayEnd(yesterday),
132
+ };
133
+ }
134
+
135
+ if (dateFilterKey === '7d') {
136
+ return {
137
+ afterDate: resolveRelativeStart(now, 7, relativeMode),
138
+ beforeDate: createRangeEnd(now, useCurrentMoment),
139
+ };
140
+ }
141
+
142
+ if (dateFilterKey === '30d') {
143
+ return {
144
+ afterDate: resolveRelativeStart(now, 30, relativeMode),
145
+ beforeDate: createRangeEnd(now, useCurrentMoment),
146
+ };
147
+ }
148
+
149
+ if (dateFilterKey === '90d') {
150
+ return {
151
+ afterDate: resolveRelativeStart(now, 90, relativeMode),
152
+ beforeDate: createRangeEnd(now, useCurrentMoment),
153
+ };
154
+ }
155
+
156
+ if (dateFilterKey === 'this_month') {
157
+ return {
158
+ afterDate: new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0),
159
+ beforeDate: createRangeEnd(now, useCurrentMoment),
160
+ };
161
+ }
162
+
163
+ if (dateFilterKey === 'last_month') {
164
+ return {
165
+ afterDate: new Date(now.getFullYear(), now.getMonth() - 1, 1, 0, 0, 0, 0),
166
+ beforeDate: new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999),
167
+ };
168
+ }
169
+
170
+ if (dateFilterKey === 'custom') {
171
+ const afterDate = parseDateInput(customRange?.from);
172
+ const beforeDate = parseDateInput(customRange?.to);
173
+
174
+ if (afterDate) {
175
+ afterDate.setHours(0, 0, 0, 0);
176
+ }
177
+
178
+ if (beforeDate) {
179
+ beforeDate.setHours(23, 59, 59, 999);
180
+ }
181
+
182
+ return {
183
+ afterDate,
184
+ beforeDate,
185
+ };
186
+ }
187
+
188
+ return {
189
+ afterDate: null,
190
+ beforeDate: null,
191
+ };
192
+ };
193
+
194
+ export const DEFAULT_DATE_FILTER_KEY = 'today';
195
+
196
+ export const DEFAULT_DATE_FILTER_OPTION_KEYS = [
197
+ 'all',
198
+ 'today',
199
+ 'yesterday',
200
+ '7d',
201
+ '30d',
202
+ 'this_month',
203
+ 'last_month',
204
+ 'custom',
205
+ ];
206
+
207
+ export const buildDateFilterOptions = (
208
+ optionKeys = DEFAULT_DATE_FILTER_OPTION_KEYS,
209
+ ) => optionKeys.map(key => ({
210
+ key,
211
+ label: DATE_FILTER_OPTION_LABELS[key] || (
212
+ key === 'yesterday'
213
+ ? resolveYesterdayLabel()
214
+ : resolveOrderLabel(DATE_FILTER_LABEL_KEYS[key] || key)
215
+ ),
216
+ }));
217
+
218
+ export const getDateRange = (
219
+ dateFilterKey,
220
+ customRange = null,
221
+ options = {},
222
+ ) => {
223
+ const { afterDate, beforeDate } = resolveDateObjectsRange(
224
+ dateFilterKey,
225
+ customRange,
226
+ options,
227
+ );
228
+
229
+ return {
230
+ after: afterDate ? formatDateToApi(afterDate) : null,
231
+ before: beforeDate ? formatDateToApi(beforeDate) : null,
232
+ };
233
+ };
234
+
235
+ export const resolveDateRangeSummary = (
236
+ dateFilterKey,
237
+ customRange = null,
238
+ options = {},
239
+ ) => {
240
+ const { afterDate, beforeDate } = resolveDateObjectsRange(
241
+ dateFilterKey,
242
+ customRange,
243
+ options,
244
+ );
245
+
246
+ if (!afterDate || !beforeDate) {
247
+ return afterDate ? formatDateLabel(afterDate) : beforeDate ? formatDateLabel(beforeDate) : '';
248
+ }
249
+
250
+ const afterLabel = formatDateLabel(afterDate);
251
+ const beforeLabel = formatDateLabel(beforeDate);
252
+
253
+ if (afterLabel === beforeLabel) {
254
+ return afterLabel;
255
+ }
256
+
257
+ return `${afterLabel} - ${beforeLabel}`;
258
+ };
259
+
260
+ export const resolveDateFilterTitle = () =>
261
+ resolveOrderLabel('period');
262
+
263
+ export const resolveDateFilterCurrentLabel = () =>
264
+ resolveOrderLabel('current_date');