@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,760 @@
1
+ import React, {useCallback, useEffect, useMemo, useState} from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ ScrollView,
5
+ Text,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ View,
9
+ } from 'react-native';
10
+ import {SafeAreaView} from 'react-native-safe-area-context';
11
+ import {useFocusEffect} from '@react-navigation/native';
12
+ import Icon from 'react-native-vector-icons/MaterialIcons';
13
+ import {useStore} from '@store';
14
+ import CompactFilterSelector from '@controleonline/ui-default/src/react/components/filters/CompactFilterSelector';
15
+ import DateShortcutFilter from '@controleonline/ui-default/src/react/components/filters/DateShortcutFilter';
16
+ import EntityLogContent from '@controleonline/ui-common/src/react/components/EntityLogContent';
17
+ import Formatter from '@controleonline/ui-common/src/utils/formatter';
18
+ import {resolveStoreConfigByClassName} from '@controleonline/ui-common/src/react/utils/storeColumns';
19
+ import {
20
+ getDateRange,
21
+ resolveDateRangeSummary,
22
+ } from '@controleonline/ui-common/src/react/utils/dateRangeFilter';
23
+ import createStyles from './GenericLogPage.styles';
24
+
25
+ const TYPE_META = {
26
+ entity: {
27
+ color: '#2563EB',
28
+ icon: 'account-tree',
29
+ label: 'Entidade',
30
+ },
31
+ operation_patterns: {
32
+ color: '#D97706',
33
+ icon: 'schema',
34
+ label: 'Padrao operacional',
35
+ },
36
+ generic: {
37
+ color: '#0F766E',
38
+ icon: 'notes',
39
+ label: 'Generico',
40
+ },
41
+ };
42
+
43
+ const ACTION_META = {
44
+ insert: {
45
+ color: '#16A34A',
46
+ icon: 'add-circle',
47
+ label: 'Criado',
48
+ },
49
+ update: {
50
+ color: '#2563EB',
51
+ icon: 'edit-note',
52
+ label: 'Alterado',
53
+ },
54
+ delete: {
55
+ color: '#DC2626',
56
+ icon: 'delete-outline',
57
+ label: 'Removido',
58
+ },
59
+ info: {
60
+ color: '#0F766E',
61
+ icon: 'info-outline',
62
+ label: 'Info',
63
+ },
64
+ notice: {
65
+ color: '#0369A1',
66
+ icon: 'notifications-none',
67
+ label: 'Aviso',
68
+ },
69
+ warning: {
70
+ color: '#D97706',
71
+ icon: 'warning-amber',
72
+ label: 'Alerta',
73
+ },
74
+ error: {
75
+ color: '#DC2626',
76
+ icon: 'error-outline',
77
+ label: 'Erro',
78
+ },
79
+ critical: {
80
+ color: '#991B1B',
81
+ icon: 'report-gmailerrorred',
82
+ label: 'Critico',
83
+ },
84
+ debug: {
85
+ color: '#475569',
86
+ icon: 'bug-report',
87
+ label: 'Debug',
88
+ },
89
+ };
90
+
91
+ const KNOWN_LOG_TYPES = ['entity', 'operation_patterns', 'generic'];
92
+
93
+ const buildEntityIri = (resourceEndpoint, entityId) => {
94
+ const normalizedEndpoint = String(resourceEndpoint || '').replace(/^\/+|\/+$/g, '');
95
+ if (!normalizedEndpoint || !entityId) {
96
+ return '';
97
+ }
98
+
99
+ return `/${normalizedEndpoint}/${entityId}`;
100
+ };
101
+
102
+ const normalizeText = value => String(value || '').trim();
103
+ const normalizeFilterKey = value => normalizeText(value).toLowerCase();
104
+
105
+ const formatHumanLabel = value =>
106
+ String(value || '')
107
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
108
+ .replace(/[_-]+/g, ' ')
109
+ .replace(/\s+/g, ' ')
110
+ .trim();
111
+
112
+ const getShortClassName = className => {
113
+ const parts = normalizeText(className).split('\\').filter(Boolean);
114
+ return parts[parts.length - 1] || normalizeText(className);
115
+ };
116
+
117
+ const formatClassLabel = className => {
118
+ const shortClassName = getShortClassName(className);
119
+ return formatHumanLabel(shortClassName) || shortClassName || 'Sem classe';
120
+ };
121
+
122
+ const resolvePayload = log => {
123
+ if (log?.payload && typeof log.payload === 'object' && !Array.isArray(log.payload)) {
124
+ return log.payload;
125
+ }
126
+
127
+ if (typeof log?.object === 'string') {
128
+ try {
129
+ const parsed = JSON.parse(log.object);
130
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
131
+ } catch {
132
+ return {};
133
+ }
134
+ }
135
+
136
+ return {};
137
+ };
138
+
139
+ const formatValue = value => {
140
+ if (value === null || value === undefined || value === '') {
141
+ return '—';
142
+ }
143
+
144
+ if (typeof value === 'boolean') {
145
+ return value ? 'Sim' : 'Nao';
146
+ }
147
+
148
+ if (typeof value === 'string') {
149
+ return value;
150
+ }
151
+
152
+ if (typeof value === 'number') {
153
+ return String(value);
154
+ }
155
+
156
+ try {
157
+ return JSON.stringify(value, null, 2);
158
+ } catch {
159
+ return String(value);
160
+ }
161
+ };
162
+
163
+ const getTypeMeta = type => TYPE_META[normalizeFilterKey(type)] || {
164
+ color: '#64748B',
165
+ icon: 'history',
166
+ label: formatHumanLabel(type) || 'Outro',
167
+ };
168
+
169
+ const getActionMeta = action => ACTION_META[normalizeFilterKey(action)] || {
170
+ color: '#64748B',
171
+ icon: 'history',
172
+ label: 'Evento',
173
+ };
174
+
175
+ const buildLogKey = log =>
176
+ String(log?.id || `${log?.type || 'log'}-${log?.class || 'no-class'}-${log?.row || 'no-row'}-${log?.createdAt || 'no-date'}`);
177
+
178
+ const buildTypeOptions = items => {
179
+ const typeKeys = new Set(KNOWN_LOG_TYPES);
180
+ items.forEach(log => {
181
+ const key = normalizeFilterKey(log?.type);
182
+ if (key) {
183
+ typeKeys.add(key);
184
+ }
185
+ });
186
+
187
+ return [
188
+ {key: 'all', label: 'Todos'},
189
+ ...Array.from(typeKeys).map(key => ({
190
+ key,
191
+ label: getTypeMeta(key).label,
192
+ })),
193
+ ];
194
+ };
195
+
196
+ const buildQuickClassOptions = items => {
197
+ const counts = new Map();
198
+
199
+ items.forEach(log => {
200
+ if (normalizeFilterKey(log?.type) !== 'entity' || !normalizeText(log?.class)) {
201
+ return;
202
+ }
203
+
204
+ const key = getShortClassName(log.class);
205
+ counts.set(key, (counts.get(key) || 0) + 1);
206
+ });
207
+
208
+ return Array.from(counts.entries())
209
+ .sort((left, right) => {
210
+ if (right[1] !== left[1]) {
211
+ return right[1] - left[1];
212
+ }
213
+
214
+ return left[0].localeCompare(right[0], 'pt-BR');
215
+ })
216
+ .slice(0, 12)
217
+ .map(([value]) => ({
218
+ label: formatClassLabel(value),
219
+ value,
220
+ }));
221
+ };
222
+
223
+ function MetaBadge({meta, styles}) {
224
+ return (
225
+ <View
226
+ style={[
227
+ styles.badge,
228
+ {
229
+ borderColor: `${meta.color}44`,
230
+ backgroundColor: `${meta.color}14`,
231
+ },
232
+ ]}>
233
+ <Icon name={meta.icon} size={14} color={meta.color} />
234
+ <Text style={[styles.badgeText, {color: meta.color}]}>
235
+ {meta.label}
236
+ </Text>
237
+ </View>
238
+ );
239
+ }
240
+
241
+ function PayloadFieldList({entries, styles}) {
242
+ if (!entries.length) {
243
+ return null;
244
+ }
245
+
246
+ return (
247
+ <View style={styles.payloadList}>
248
+ {entries.map(([field, value]) => (
249
+ <View key={field} style={styles.payloadRow}>
250
+ <Text style={styles.payloadLabel}>{formatHumanLabel(field) || field}</Text>
251
+ <View style={styles.payloadValueBox}>
252
+ <Text style={styles.payloadValue}>{formatValue(value)}</Text>
253
+ </View>
254
+ </View>
255
+ ))}
256
+ </View>
257
+ );
258
+ }
259
+
260
+ function EntityLogCard({
261
+ log,
262
+ isExpanded,
263
+ onOpenFullHistory,
264
+ onToggleExpand,
265
+ styles,
266
+ }) {
267
+ const payload = useMemo(() => resolvePayload(log), [log]);
268
+ const payloadEntries = useMemo(() => Object.entries(payload || {}), [payload]);
269
+ const typeMeta = useMemo(() => getTypeMeta(log?.type), [log?.type]);
270
+ const actionMeta = useMemo(() => getActionMeta(log?.action), [log?.action]);
271
+ const logDate = useMemo(
272
+ () => Formatter.formatDateYmdTodmY(log?.createdAt, true),
273
+ [log?.createdAt],
274
+ );
275
+ const entityTitle = useMemo(
276
+ () => `${formatClassLabel(log?.class)} #${log?.row || '--'}`,
277
+ [log?.class, log?.row],
278
+ );
279
+ const previewFields = useMemo(
280
+ () => payloadEntries.slice(0, 4).map(([field]) => formatHumanLabel(field) || field),
281
+ [payloadEntries],
282
+ );
283
+ const storeConfig = useMemo(
284
+ () => resolveStoreConfigByClassName(log?.class),
285
+ [log?.class],
286
+ );
287
+ const entityIri = useMemo(
288
+ () => buildEntityIri(storeConfig?.resourceEndpoint, log?.row),
289
+ [log?.row, storeConfig?.resourceEndpoint],
290
+ );
291
+
292
+ return (
293
+ <View style={styles.card}>
294
+ <View style={styles.cardHeader}>
295
+ <View style={styles.badgesRow}>
296
+ <MetaBadge meta={typeMeta} styles={styles} />
297
+ <MetaBadge meta={actionMeta} styles={styles} />
298
+ </View>
299
+
300
+ <Text style={styles.date}>{logDate || 'Sem data'}</Text>
301
+ </View>
302
+
303
+ <Text style={styles.entityTitle}>{entityTitle}</Text>
304
+
305
+ {!!log?.userDisplayName && (
306
+ <Text style={styles.metaText}>Usuario: {log.userDisplayName}</Text>
307
+ )}
308
+
309
+ {!!previewFields.length ? (
310
+ <Text style={styles.previewText}>
311
+ Campos afetados: {previewFields.join(', ')}
312
+ </Text>
313
+ ) : (
314
+ <Text style={styles.previewText}>Sem diff detalhado registrado neste evento.</Text>
315
+ )}
316
+
317
+ <View style={styles.cardActionsRow}>
318
+ <TouchableOpacity
319
+ activeOpacity={0.9}
320
+ onPress={onToggleExpand}
321
+ style={styles.secondaryActionButton}>
322
+ <Icon
323
+ name={isExpanded ? 'expand-less' : 'expand-more'}
324
+ size={18}
325
+ color="#0F172A"
326
+ />
327
+ <Text style={styles.secondaryActionButtonText}>
328
+ {isExpanded ? 'Recolher timeline' : 'Expandir timeline'}
329
+ </Text>
330
+ </TouchableOpacity>
331
+
332
+ <TouchableOpacity
333
+ activeOpacity={0.9}
334
+ onPress={onOpenFullHistory}
335
+ style={styles.primaryActionButton}>
336
+ <Icon name="open-in-new" size={16} color="#FFFFFF" />
337
+ <Text style={styles.primaryActionButtonText}>Abrir historico</Text>
338
+ </TouchableOpacity>
339
+ </View>
340
+
341
+ {isExpanded ? (
342
+ <View style={styles.expandedEntityWrap}>
343
+ <EntityLogContent
344
+ entity={entityIri ? {'@id': entityIri, id: log?.row} : {id: log?.row}}
345
+ entityClass={normalizeText(log?.class)}
346
+ entityId={Number(log?.row) || null}
347
+ entityIri={entityIri}
348
+ entityLabel={entityTitle}
349
+ />
350
+ </View>
351
+ ) : null}
352
+ </View>
353
+ );
354
+ }
355
+
356
+ function OtherLogCard({log, styles}) {
357
+ const payload = useMemo(() => resolvePayload(log), [log]);
358
+ const typeMeta = useMemo(() => getTypeMeta(log?.type), [log?.type]);
359
+ const actionMeta = useMemo(() => getActionMeta(log?.action), [log?.action]);
360
+ const logDate = useMemo(
361
+ () => Formatter.formatDateYmdTodmY(log?.createdAt, true),
362
+ [log?.createdAt],
363
+ );
364
+ const payloadEntries = useMemo(
365
+ () => Object.entries(payload || {}).filter(([field]) => field !== 'message'),
366
+ [payload],
367
+ );
368
+ const primaryMessage = useMemo(() => {
369
+ const payloadMessage =
370
+ typeof payload?.message === 'string' ? payload.message.trim() : '';
371
+
372
+ if (payloadMessage) {
373
+ return payloadMessage;
374
+ }
375
+
376
+ return normalizeText(log?.action) || 'Sem descricao registrada.';
377
+ }, [log?.action, payload?.message]);
378
+ const rawAction = normalizeText(log?.action);
379
+ const showRawAction = rawAction && rawAction !== actionMeta.label && rawAction !== primaryMessage;
380
+
381
+ return (
382
+ <View style={styles.card}>
383
+ <View style={styles.cardHeader}>
384
+ <View style={styles.badgesRow}>
385
+ <MetaBadge meta={typeMeta} styles={styles} />
386
+ <MetaBadge meta={actionMeta} styles={styles} />
387
+ </View>
388
+
389
+ <Text style={styles.date}>{logDate || 'Sem data'}</Text>
390
+ </View>
391
+
392
+ {!!normalizeText(log?.class) && (
393
+ <Text style={styles.classTitle}>{formatClassLabel(log.class)}</Text>
394
+ )}
395
+
396
+ {!!log?.userDisplayName && (
397
+ <Text style={styles.metaText}>Usuario: {log.userDisplayName}</Text>
398
+ )}
399
+
400
+ <View style={styles.messageBox}>
401
+ <Text style={styles.message}>{primaryMessage}</Text>
402
+ </View>
403
+
404
+ {showRawAction ? (
405
+ <Text style={styles.metaText}>Acao registrada: {rawAction}</Text>
406
+ ) : null}
407
+
408
+ <PayloadFieldList entries={payloadEntries} styles={styles} />
409
+ </View>
410
+ );
411
+ }
412
+
413
+ export default function GenericLogPage({navigation}) {
414
+ const styles = useMemo(() => createStyles(), []);
415
+ const entityLogStore = useStore('entity_log');
416
+ const entityLogActions = entityLogStore?.actions || {};
417
+ const [logsState, setLogsState] = useState({
418
+ error: '',
419
+ items: [],
420
+ status: 'idle',
421
+ totalItems: 0,
422
+ });
423
+ const [selectedType, setSelectedType] = useState('all');
424
+ const [dateFilterKey, setDateFilterKey] = useState('all');
425
+ const [customRange, setCustomRange] = useState({
426
+ from: '',
427
+ to: '',
428
+ });
429
+ const [classFilterInput, setClassFilterInput] = useState('');
430
+ const [appliedClassFilter, setAppliedClassFilter] = useState('');
431
+ const [expandedEntityKey, setExpandedEntityKey] = useState('');
432
+
433
+ const loadLogs = useCallback(async () => {
434
+ const {after, before} = getDateRange(dateFilterKey, customRange, {
435
+ useCurrentMoment: true,
436
+ });
437
+
438
+ setLogsState(current => ({
439
+ ...current,
440
+ error: '',
441
+ status: 'loading',
442
+ }));
443
+
444
+ try {
445
+ const response = await entityLogActions.getTimeline({
446
+ ...(selectedType !== 'all' ? {type: selectedType} : {}),
447
+ ...(after ? {'createdAt[after]': after} : {}),
448
+ ...(before ? {'createdAt[before]': before} : {}),
449
+ ...(appliedClassFilter ? {class: appliedClassFilter} : {}),
450
+ });
451
+
452
+ const items = Array.isArray(response?.items) ? response.items : [];
453
+ setLogsState({
454
+ error: '',
455
+ items,
456
+ status: 'success',
457
+ totalItems: Number(response?.totalItems || items.length || 0),
458
+ });
459
+ } catch (error) {
460
+ setLogsState({
461
+ error: error?.message || 'Erro ao carregar logs.',
462
+ items: [],
463
+ status: 'error',
464
+ totalItems: 0,
465
+ });
466
+ }
467
+ }, [appliedClassFilter, customRange, dateFilterKey, entityLogActions, selectedType]);
468
+
469
+ const typeOptions = useMemo(
470
+ () => buildTypeOptions(logsState.items),
471
+ [logsState.items],
472
+ );
473
+
474
+ const quickClassOptions = useMemo(
475
+ () => buildQuickClassOptions(logsState.items),
476
+ [logsState.items],
477
+ );
478
+
479
+ const activeDateSummary = useMemo(
480
+ () => resolveDateRangeSummary(dateFilterKey, customRange, {useCurrentMoment: true}),
481
+ [customRange, dateFilterKey],
482
+ );
483
+
484
+ const hasActiveFilters = useMemo(
485
+ () =>
486
+ selectedType !== 'all' ||
487
+ Boolean(appliedClassFilter) ||
488
+ Boolean(activeDateSummary),
489
+ [activeDateSummary, appliedClassFilter, selectedType],
490
+ );
491
+
492
+ const resultsSummary = useMemo(() => {
493
+ const loadedCount = logsState.items.length;
494
+ const totalCount = logsState.totalItems || loadedCount;
495
+ const countLabel =
496
+ loadedCount === totalCount
497
+ ? `${loadedCount} logs`
498
+ : `${loadedCount} de ${totalCount} logs`;
499
+
500
+ if (!activeDateSummary) {
501
+ return countLabel;
502
+ }
503
+
504
+ return `${countLabel} no periodo ${activeDateSummary}`;
505
+ }, [activeDateSummary, logsState.items.length, logsState.totalItems]);
506
+ const selectedTypeLabel = useMemo(
507
+ () => typeOptions.find(option => option.key === selectedType)?.label || 'Todos',
508
+ [selectedType, typeOptions],
509
+ );
510
+ const selectedQuickClassLabel = useMemo(() => {
511
+ if (!appliedClassFilter) {
512
+ return 'Sugestoes';
513
+ }
514
+
515
+ return quickClassOptions.find(
516
+ option => normalizeFilterKey(option.value) === normalizeFilterKey(appliedClassFilter),
517
+ )?.label || formatClassLabel(appliedClassFilter);
518
+ }, [appliedClassFilter, quickClassOptions]);
519
+
520
+ const applyClassFilter = useCallback(() => {
521
+ setAppliedClassFilter(normalizeText(classFilterInput));
522
+ }, [classFilterInput]);
523
+
524
+ const clearAllFilters = useCallback(() => {
525
+ setSelectedType('all');
526
+ setDateFilterKey('all');
527
+ setCustomRange({from: '', to: ''});
528
+ setClassFilterInput('');
529
+ setAppliedClassFilter('');
530
+ setExpandedEntityKey('');
531
+ }, []);
532
+
533
+ const openEntityHistory = useCallback((log) => {
534
+ const storeConfig = resolveStoreConfigByClassName(log?.class);
535
+ navigation.navigate('EntityLogPage', {
536
+ id: log?.row,
537
+ store: storeConfig?.storeName || '',
538
+ entityClass: normalizeText(log?.class),
539
+ entityLabel: `${formatClassLabel(log?.class)} #${log?.row || '--'}`,
540
+ });
541
+ }, [navigation]);
542
+
543
+ useEffect(() => {
544
+ navigation.setOptions({
545
+ title: 'Logs',
546
+ });
547
+ }, [navigation]);
548
+
549
+ useEffect(() => {
550
+ setExpandedEntityKey('');
551
+ }, [appliedClassFilter, dateFilterKey, logsState.items, selectedType]);
552
+
553
+ useFocusEffect(
554
+ useCallback(() => {
555
+ void loadLogs();
556
+ }, [loadLogs]),
557
+ );
558
+
559
+ return (
560
+ <SafeAreaView edges={['left', 'right', 'bottom']} style={styles.safeArea}>
561
+ <View style={styles.container}>
562
+ <View style={styles.heroCard}>
563
+ <View style={styles.heroHeader}>
564
+ <View style={styles.heroCopy}>
565
+ <Text style={styles.heroTitle}>Logs</Text>
566
+ <Text style={styles.heroSubtitle}>
567
+ Timeline global com todos os tipos de log. Para registros de entidade, a expansao reutiliza o historico da propria entidade.
568
+ </Text>
569
+ </View>
570
+
571
+ <TouchableOpacity
572
+ activeOpacity={0.85}
573
+ onPress={() => void loadLogs()}
574
+ style={styles.refreshButton}>
575
+ <Icon name="refresh" size={18} color="#2563EB" />
576
+ </TouchableOpacity>
577
+ </View>
578
+
579
+ <Text style={styles.heroSummary}>{resultsSummary}</Text>
580
+ </View>
581
+
582
+ <ScrollView
583
+ style={styles.scroll}
584
+ contentContainerStyle={styles.scrollContent}
585
+ showsVerticalScrollIndicator={false}>
586
+ <View style={styles.filtersCard}>
587
+ <View style={styles.filterHeaderRow}>
588
+ <View style={styles.filterHeaderCopy}>
589
+ <Text style={styles.filterTitle}>Filtros</Text>
590
+ <Text style={styles.filterSubtitle}>
591
+ Filtre por periodo, tipo real do log e classe da entidade. Para integracao, use `entity` e pesquise `Integration`.
592
+ </Text>
593
+ </View>
594
+
595
+ {hasActiveFilters ? (
596
+ <TouchableOpacity
597
+ activeOpacity={0.9}
598
+ onPress={clearAllFilters}
599
+ style={styles.clearButton}>
600
+ <Text style={styles.clearButtonText}>Limpar</Text>
601
+ </TouchableOpacity>
602
+ ) : null}
603
+ </View>
604
+
605
+ <View style={styles.filterGroup}>
606
+ <Text style={styles.filterLabel}>Periodo</Text>
607
+ <DateShortcutFilter
608
+ value={dateFilterKey}
609
+ onChange={setDateFilterKey}
610
+ customRange={customRange}
611
+ onCustomRangeChange={setCustomRange}
612
+ colors={{
613
+ accent: '#2563EB',
614
+ appBg: 'transparent',
615
+ border: '#CBD5E1',
616
+ borderSoft: '#E2E8F0',
617
+ cardBg: '#FFFFFF',
618
+ cardBgSoft: '#F8FAFC',
619
+ danger: '#DC2626',
620
+ isLight: true,
621
+ panelBg: '#EFF6FF',
622
+ pillTextDark: '#FFFFFF',
623
+ textPrimary: '#0F172A',
624
+ textSecondary: '#64748B',
625
+ }}
626
+ />
627
+
628
+ {!!activeDateSummary && (
629
+ <Text style={styles.filterHint}>Periodo atual: {activeDateSummary}</Text>
630
+ )}
631
+ </View>
632
+
633
+ <View style={styles.filterGroup}>
634
+ <Text style={styles.filterLabel}>Tipo de log</Text>
635
+ <CompactFilterSelector
636
+ icon="filter"
637
+ label={selectedTypeLabel}
638
+ title="Tipo de log"
639
+ accentColor="#2563EB"
640
+ active={selectedType !== 'all'}
641
+ options={typeOptions}
642
+ selectedKey={selectedType}
643
+ onSelect={optionKey => {
644
+ setSelectedType(optionKey);
645
+ return true;
646
+ }}
647
+ />
648
+ </View>
649
+
650
+ <View style={styles.filterGroup}>
651
+ <Text style={styles.filterLabel}>Classe / origem</Text>
652
+ <View style={styles.textFilterRow}>
653
+ <TextInput
654
+ value={classFilterInput}
655
+ onChangeText={setClassFilterInput}
656
+ placeholder="Ex.: Integration, Device, execute_operation"
657
+ placeholderTextColor="#94A3B8"
658
+ autoCapitalize="none"
659
+ autoCorrect={false}
660
+ style={styles.textFilterInput}
661
+ />
662
+
663
+ <TouchableOpacity
664
+ activeOpacity={0.9}
665
+ onPress={applyClassFilter}
666
+ style={styles.primaryButton}>
667
+ <Text style={styles.primaryButtonText}>Aplicar</Text>
668
+ </TouchableOpacity>
669
+ </View>
670
+
671
+ {!!appliedClassFilter && (
672
+ <Text style={styles.filterHint}>Classe aplicada: {appliedClassFilter}</Text>
673
+ )}
674
+
675
+ {!!quickClassOptions.length && (
676
+ <CompactFilterSelector
677
+ icon="layers"
678
+ label={selectedQuickClassLabel}
679
+ title="Sugestoes de classe"
680
+ accentColor="#2563EB"
681
+ active={Boolean(appliedClassFilter)}
682
+ options={quickClassOptions.map(option => ({
683
+ key: option.value,
684
+ label: option.label,
685
+ }))}
686
+ selectedKey={appliedClassFilter}
687
+ onSelect={optionKey => {
688
+ setClassFilterInput(optionKey);
689
+ setAppliedClassFilter(optionKey);
690
+ return true;
691
+ }}
692
+ />
693
+ )}
694
+ </View>
695
+ </View>
696
+
697
+ {logsState.status === 'loading' ? (
698
+ <View style={styles.stateBox}>
699
+ <ActivityIndicator size="small" color="#2563EB" />
700
+ <Text style={styles.stateText}>Carregando logs...</Text>
701
+ </View>
702
+ ) : null}
703
+
704
+ {logsState.status === 'error' ? (
705
+ <View style={styles.stateBox}>
706
+ <Icon name="error-outline" size={22} color="#DC2626" />
707
+ <Text style={styles.stateTitle}>Nao foi possivel carregar</Text>
708
+ <Text style={styles.stateText}>{logsState.error}</Text>
709
+ </View>
710
+ ) : null}
711
+
712
+ {logsState.status === 'success' && !logsState.items.length ? (
713
+ <View style={styles.stateBox}>
714
+ <Icon name="history-toggle-off" size={22} color="#94A3B8" />
715
+ <Text style={styles.stateTitle}>Nenhum log encontrado</Text>
716
+ <Text style={styles.stateText}>
717
+ Nenhum registro corresponde aos filtros aplicados.
718
+ </Text>
719
+ </View>
720
+ ) : null}
721
+
722
+ {logsState.status === 'success' && !!logsState.items.length ? (
723
+ <View style={styles.list}>
724
+ {logsState.items.map(log => {
725
+ const logKey = buildLogKey(log);
726
+ const isEntityLog =
727
+ normalizeFilterKey(log?.type) === 'entity' &&
728
+ normalizeText(log?.class) &&
729
+ Number(log?.row) > 0;
730
+
731
+ if (isEntityLog) {
732
+ return (
733
+ <EntityLogCard
734
+ key={logKey}
735
+ isExpanded={expandedEntityKey === logKey}
736
+ log={log}
737
+ onOpenFullHistory={() => openEntityHistory(log)}
738
+ onToggleExpand={() =>
739
+ setExpandedEntityKey(current => current === logKey ? '' : logKey)
740
+ }
741
+ styles={styles}
742
+ />
743
+ );
744
+ }
745
+
746
+ return (
747
+ <OtherLogCard
748
+ key={logKey}
749
+ log={log}
750
+ styles={styles}
751
+ />
752
+ );
753
+ })}
754
+ </View>
755
+ ) : null}
756
+ </ScrollView>
757
+ </View>
758
+ </SafeAreaView>
759
+ );
760
+ }