@controleonline/ui-common 1.2.80 → 1.2.81

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 (90) hide show
  1. package/AGENTS.md +8 -0
  2. package/package.json +9 -3
  3. package/src/api/fetch.js +7 -7
  4. package/src/react/components/AddImportModal.js +6 -2
  5. package/src/react/components/BackgroundRuntimeBridge.js +201 -215
  6. package/src/react/components/ConfirmModal.js +44 -9
  7. package/src/react/components/ConfirmModal.styles.js +55 -41
  8. package/src/react/components/CopyDeviceConfigModal.js +193 -0
  9. package/src/react/components/CopyDeviceConfigModal.styles.js +123 -0
  10. package/src/react/components/DefaultProvider.native.js +22 -0
  11. package/src/react/components/DefaultProvider.web.js +25 -1
  12. package/src/react/components/EntityLogBranch.js +482 -0
  13. package/src/react/components/EntityLogCards.js +369 -0
  14. package/src/react/components/EntityLogContent.js +113 -794
  15. package/src/react/components/LinkedOrderProductsTab.js +3 -3
  16. package/src/react/components/PrintService.js +130 -409
  17. package/src/react/components/RemoteCheckoutService.js +2 -2
  18. package/src/react/components/RuntimeBottomNavigationBar.js +141 -146
  19. package/src/react/components/RuntimeFooterMarqueeText.js +137 -0
  20. package/src/react/components/RuntimeInfoFooter.js +12 -15
  21. package/src/react/components/SystemErrorToast.js +49 -29
  22. package/src/react/components/SystemErrorToast.styles.js +43 -38
  23. package/src/react/components/UserAvatar.js +120 -28
  24. package/src/react/config/deviceConfigBootstrap.js +330 -304
  25. package/src/react/hooks/usePrintSpoolEffects.js +161 -0
  26. package/src/react/pages/DeviceDetailPage.js +6 -2882
  27. package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +233 -0
  28. package/src/react/pages/Devices/detail/DeviceDetailHeader.js +138 -0
  29. package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +167 -0
  30. package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +216 -0
  31. package/src/react/pages/Devices/detail/DeviceDetailPaymentSection.js +40 -0
  32. package/src/react/pages/Devices/detail/DeviceDetailPdvConfigSection.js +390 -0
  33. package/src/react/pages/Devices/detail/DeviceDetailRenderers.js +135 -0
  34. package/src/react/pages/Devices/detail/DeviceDetailScreen.js +493 -0
  35. package/src/react/pages/Devices/detail/OptionButtonChip.js +65 -0
  36. package/src/react/pages/Devices/detail/deviceDetailConstants.js +36 -0
  37. package/src/react/pages/Devices/detail/deviceDetailHelpers.js +70 -0
  38. package/src/react/pages/Devices/detail/useDeviceDetailActions.js +483 -0
  39. package/src/react/pages/Devices/detail/useDeviceDetailCopyConfig.js +87 -0
  40. package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +327 -0
  41. package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +429 -0
  42. package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +484 -0
  43. package/src/react/pages/Devices/detail/useDeviceDetailStateB.js +71 -0
  44. package/src/react/pages/Devices/deviceTypes/index.js +2 -0
  45. package/src/react/pages/Devices/deviceTypes/pdv.js +11 -0
  46. package/src/react/pages/Imports.js +3 -2
  47. package/src/react/pages/IntegrationConfigFields.js +126 -0
  48. package/src/react/pages/IntegrationConfigPage.js +122 -591
  49. package/src/react/pages/IntegrationConfigPage.utils.js +95 -0
  50. package/src/react/print/usePrintButtonController.js +9 -1
  51. package/src/react/services/Getnet/Checkout.js +81 -0
  52. package/src/react/services/Getnet/Getnet.js +77 -0
  53. package/src/react/services/paymentGatewayExecution.js +20 -0
  54. package/src/react/utils/bottomNavigationToolbar.js +161 -0
  55. package/src/react/utils/commercialDocumentOrders.js +18 -6
  56. package/src/react/utils/copyDeviceConfigs.js +198 -0
  57. package/src/react/utils/fileUrl.js +43 -0
  58. package/src/react/utils/importStatus.js +19 -10
  59. package/src/react/utils/logSettings.js +7 -7
  60. package/src/react/utils/maintenanceSettings.js +16 -0
  61. package/src/react/utils/paymentDevices.js +6 -0
  62. package/src/react/utils/printManagedPrinter.js +45 -0
  63. package/src/react/utils/printRouting.js +63 -0
  64. package/src/react/utils/printSpoolUtils.js +119 -0
  65. package/src/react/utils/printerDevices.js +368 -368
  66. package/src/react/utils/runtimeFooter.js +10 -19
  67. package/src/react/utils/shopFranchises.js +186 -65
  68. package/src/react/utils/systemErrorMessage.js +71 -67
  69. package/src/store/device_config/customActions.js +119 -30
  70. package/src/tests/browser/manager/bottom-navigation-role-toolbar.spec.js +353 -0
  71. package/src/tests/browser/manager/device-detail-alias-save.spec.js +345 -0
  72. package/src/tests/browser/manager/device-detail-copy-config.spec.js +365 -0
  73. package/src/tests/browser/manager/runtime-footer-marquee.spec.js +262 -0
  74. package/src/tests/browser/manager/theme-confirm-toast.spec.js +236 -0
  75. package/src/tests/react/components/RuntimeFooterMarqueeText.test.js +171 -0
  76. package/src/tests/react/components/UserAvatar.test.js +6 -2
  77. package/src/tests/react/components/themeAwareModals.test.mjs +60 -0
  78. package/src/tests/react/config/deviceConfigBootstrap.test.js +104 -66
  79. package/src/tests/react/pages/deviceDetailDelete.test.js +78 -0
  80. package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +48 -0
  81. package/src/tests/react/utils/bottomNavigationToolbar.test.js +222 -0
  82. package/src/tests/react/utils/copyDeviceConfigs.test.mjs +127 -0
  83. package/src/tests/react/utils/fileUrl.test.js +38 -0
  84. package/src/tests/react/utils/importStatus.test.js +26 -9
  85. package/src/tests/react/utils/passwordPolicy.test.js +27 -0
  86. package/src/tests/react/utils/printRouting.test.mjs +86 -0
  87. package/src/tests/react/utils/printSpoolUtils.test.mjs +52 -0
  88. package/src/tests/react/utils/runtimeFooter.test.js +7 -0
  89. package/src/tests/react/utils/shopFranchises.test.js +121 -115
  90. package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
@@ -0,0 +1,482 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ Image,
5
+ ScrollView,
6
+ Text,
7
+ TouchableOpacity,
8
+ View,
9
+ } from 'react-native';
10
+ import Icon from 'react-native-vector-icons/MaterialIcons';
11
+ import { useStore } from '@store';
12
+ import Formatter from '@controleonline/ui-common/src/utils/formatter';
13
+ import {
14
+ buildEntityChildren,
15
+ buildEntityKey,
16
+ buildEntityLabel,
17
+ buildEntitySummaryFields,
18
+ formatLogFieldLabel,
19
+ formatLogValue,
20
+ resolveEntityImageUrl,
21
+ } from '@controleonline/ui-common/src/react/utils/entityLog';
22
+ import { resolveStoreConfigByEntity } from '@controleonline/ui-common/src/react/utils/storeColumns';
23
+ import createStyles, {
24
+ buildEntityLogPalette,
25
+ } from './EntityLogContent.styles';
26
+
27
+ const ACTION_META = {
28
+ insert: {
29
+ color: '#16A34A',
30
+ icon: 'add-circle',
31
+ label: 'Criado',
32
+ },
33
+ update: {
34
+ color: '#2563EB',
35
+ icon: 'edit-note',
36
+ label: 'Alterado',
37
+ },
38
+ delete: {
39
+ color: '#DC2626',
40
+ icon: 'delete-outline',
41
+ label: 'Removido',
42
+ },
43
+ info: {
44
+ color: '#0F766E',
45
+ icon: 'info-outline',
46
+ label: 'Info',
47
+ },
48
+ notice: {
49
+ color: '#0369A1',
50
+ icon: 'notifications-none',
51
+ label: 'Aviso',
52
+ },
53
+ warning: {
54
+ color: '#D97706',
55
+ icon: 'warning-amber',
56
+ label: 'Alerta',
57
+ },
58
+ error: {
59
+ color: '#DC2626',
60
+ icon: 'error-outline',
61
+ label: 'Erro',
62
+ },
63
+ critical: {
64
+ color: '#991B1B',
65
+ icon: 'report-gmailerrorred',
66
+ label: 'Critico',
67
+ },
68
+ debug: {
69
+ color: '#475569',
70
+ icon: 'bug-report',
71
+ label: 'Debug',
72
+ },
73
+ };
74
+
75
+ const GENERIC_PAYLOAD_META_FIELDS = new Set(['channel', 'context', 'level', 'message']);
76
+
77
+ const buildContextEntries = context => {
78
+ if (!context) {
79
+ return [];
80
+ }
81
+
82
+ if (Array.isArray(context)) {
83
+ return context.map((value, index) => [`item_${index + 1}`, value]);
84
+ }
85
+
86
+ if (typeof context === 'object') {
87
+ return Object.entries(context);
88
+ }
89
+
90
+ return [['context', context]];
91
+ };
92
+
93
+ const formatContextValue = value => {
94
+ if (value === null || value === undefined || value === '') {
95
+ return '—';
96
+ }
97
+
98
+ if (typeof value === 'boolean') {
99
+ return value ? 'Sim' : 'Nao';
100
+ }
101
+
102
+ if (typeof value === 'string') {
103
+ return value;
104
+ }
105
+
106
+ if (typeof value === 'number') {
107
+ return String(value);
108
+ }
109
+
110
+ try {
111
+ return JSON.stringify(value, null, 2);
112
+ } catch {
113
+ return String(value);
114
+ }
115
+ };
116
+
117
+ const resolvePayload = log => {
118
+ if (log?.payload && typeof log.payload === 'object' && !Array.isArray(log.payload)) {
119
+ return log.payload;
120
+ }
121
+
122
+ if (typeof log?.object === 'string') {
123
+ try {
124
+ const parsed = JSON.parse(log.object);
125
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
126
+ } catch {
127
+ return {};
128
+ }
129
+ }
130
+
131
+ return {};
132
+ };
133
+
134
+ // O card de resumo mostra os dados essenciais da entidade expandida.
135
+
136
+ import {
137
+ EntitySummaryCard,
138
+ renderChangeValue,
139
+ LogCard,
140
+ } from './EntityLogCards';
141
+
142
+ export const EntityLogBranch = ({
143
+ autoLoad = true,
144
+ entity,
145
+ entityClass,
146
+ entityId,
147
+ entityIri = '',
148
+ entityLabel,
149
+ fetchEntityDetails,
150
+ getEntityState,
151
+ fetchLogs,
152
+ getLogsState,
153
+ onFocusBranch,
154
+ parentOffset = 0,
155
+ relationConfig,
156
+ styles,
157
+ trailKeys,
158
+ }) => {
159
+ const branchKey = useMemo(
160
+ () => buildEntityKey(entityClass, entityId),
161
+ [entityClass, entityId],
162
+ );
163
+ const [expandedRelations, setExpandedRelations] = useState({});
164
+ const [expandedItems, setExpandedItems] = useState({});
165
+ const [branchOffset, setBranchOffset] = useState(parentOffset);
166
+ const [nestedOffsets, setNestedOffsets] = useState({});
167
+ const logsState = getLogsState(entityClass, entityId);
168
+ const detailState = getEntityState(entityClass, entityId, entity);
169
+ const resolvedEntity = useMemo(() => {
170
+ if (!detailState?.item || typeof detailState.item !== 'object') {
171
+ return entity;
172
+ }
173
+
174
+ if (!entity || typeof entity !== 'object') {
175
+ return detailState.item;
176
+ }
177
+
178
+ // Mantem o seed local, mas o detalhe remoto prevalece quando trouxer mais dados.
179
+ return {
180
+ ...entity,
181
+ ...detailState.item,
182
+ };
183
+ }, [detailState?.item, entity]);
184
+ const isNestedBranch = trailKeys.length > 0;
185
+ const storeConfig = useMemo(
186
+ () =>
187
+ resolveStoreConfigByEntity({
188
+ entity: resolvedEntity,
189
+ entityIri,
190
+ }),
191
+ [entityIri, resolvedEntity],
192
+ );
193
+ const storeColumns = storeConfig?.columns || [];
194
+ const storeName = storeConfig?.storeName || '';
195
+
196
+ const branchTitle = useMemo(
197
+ () =>
198
+ entityLabel ||
199
+ buildEntityLabel({
200
+ entity: resolvedEntity,
201
+ className: entityClass,
202
+ id: entityId,
203
+ }),
204
+ [resolvedEntity, entityClass, entityId, entityLabel],
205
+ );
206
+
207
+ const children = useMemo(
208
+ () =>
209
+ buildEntityChildren(resolvedEntity, {
210
+ relationConfig,
211
+ ancestryKeys: [...trailKeys, branchKey],
212
+ }),
213
+ [branchKey, resolvedEntity, relationConfig, trailKeys],
214
+ );
215
+
216
+ useEffect(() => {
217
+ if (!autoLoad || !entityClass || !entityId) {
218
+ return;
219
+ }
220
+
221
+ void fetchLogs({
222
+ className: entityClass,
223
+ rowId: entityId,
224
+ });
225
+ void fetchEntityDetails({
226
+ className: entityClass,
227
+ rowId: entityId,
228
+ entity,
229
+ entityIri,
230
+ });
231
+ }, [
232
+ autoLoad,
233
+ entityClass,
234
+ entityId,
235
+ entity,
236
+ entityIri,
237
+ fetchEntityDetails,
238
+ fetchLogs,
239
+ ]);
240
+
241
+ useEffect(() => {
242
+ if (!isNestedBranch || !branchOffset || typeof onFocusBranch !== 'function') {
243
+ return;
244
+ }
245
+
246
+ // Depois de montar um ramo novo, trazemos o topo dele para a viewport.
247
+ const timeoutId = setTimeout(() => {
248
+ onFocusBranch(Math.max(0, branchOffset - 18));
249
+ }, 120);
250
+
251
+ return () => clearTimeout(timeoutId);
252
+ }, [branchOffset, isNestedBranch, onFocusBranch]);
253
+
254
+ const toggleRelation = useCallback(key => {
255
+ setExpandedRelations(prev => ({
256
+ ...prev,
257
+ [key]: !prev[key],
258
+ }));
259
+ if (typeof onFocusBranch === 'function') {
260
+ setTimeout(() => {
261
+ onFocusBranch(Math.max(0, branchOffset - 18));
262
+ }, 60);
263
+ }
264
+ }, [branchOffset, onFocusBranch]);
265
+
266
+ const toggleItem = useCallback(key => {
267
+ setExpandedItems(prev => ({
268
+ ...prev,
269
+ [key]: !prev[key],
270
+ }));
271
+ if (typeof onFocusBranch === 'function') {
272
+ setTimeout(() => {
273
+ onFocusBranch(Math.max(0, branchOffset - 18));
274
+ }, 60);
275
+ }
276
+ }, [branchOffset, onFocusBranch]);
277
+
278
+ const registerNestedOffset = useCallback((key, layoutY) => {
279
+ setNestedOffsets(prev => {
280
+ const nextOffset = branchOffset + layoutY;
281
+ if (prev[key] === nextOffset) {
282
+ return prev;
283
+ }
284
+
285
+ return {
286
+ ...prev,
287
+ [key]: nextOffset,
288
+ };
289
+ });
290
+ }, [branchOffset]);
291
+
292
+ return (
293
+ <View
294
+ style={styles.branchCard}
295
+ onLayout={event => {
296
+ setBranchOffset(parentOffset + event.nativeEvent.layout.y);
297
+ }}>
298
+ <View style={styles.branchHeader}>
299
+ <View style={styles.branchHeaderRow}>
300
+ <View style={styles.branchTitleWrap}>
301
+ <Text style={styles.branchTitle}>{branchTitle}</Text>
302
+ <Text style={styles.branchMeta}>
303
+ {entityClass?.split('\\').pop()} #{entityId || '--'}
304
+ </Text>
305
+ </View>
306
+ </View>
307
+ </View>
308
+
309
+ <View style={styles.branchBody}>
310
+ <EntitySummaryCard
311
+ columns={storeColumns}
312
+ detailState={detailState}
313
+ entity={resolvedEntity}
314
+ entityClass={entityClass}
315
+ storeName={storeName}
316
+ styles={styles}
317
+ />
318
+
319
+ {!!children.length && (
320
+ <View style={styles.relationSection}>
321
+ <Text style={styles.sectionTitle}>Relacionamentos</Text>
322
+
323
+ {children.map(candidate => (
324
+ <View key={`${branchKey}-${candidate.key}`} style={styles.relationCollectionWrap}>
325
+ <TouchableOpacity
326
+ activeOpacity={0.85}
327
+ onPress={() => toggleRelation(candidate.key)}
328
+ style={styles.relationButton}>
329
+ <View style={styles.relationButtonHeader}>
330
+ <Text style={styles.relationButtonLabel}>
331
+ {candidate.label}
332
+ </Text>
333
+ <Icon
334
+ name={expandedRelations[candidate.key] ? 'expand-less' : 'expand-more'}
335
+ size={20}
336
+ color="#64748B"
337
+ />
338
+ </View>
339
+ <Text style={styles.relationButtonMeta}>
340
+ {candidate.isCollection
341
+ ? `${candidate.items.length} item(ns)`
342
+ : candidate.items[0]?.label || 'Ver histórico'}
343
+ </Text>
344
+ </TouchableOpacity>
345
+
346
+ {expandedRelations[candidate.key] && (
347
+ <View
348
+ style={styles.nestedWrap}
349
+ onLayout={event =>
350
+ registerNestedOffset(candidate.key, event.nativeEvent.layout.y)
351
+ }>
352
+ {candidate.items.map(item => (
353
+ <View key={item.key} style={styles.relationCollectionWrap}>
354
+ {candidate.isCollection ? (
355
+ <>
356
+ <TouchableOpacity
357
+ activeOpacity={0.85}
358
+ onPress={() => toggleItem(item.key)}
359
+ style={styles.relationButton}>
360
+ <View style={styles.relationButtonHeader}>
361
+ <Text style={styles.relationButtonLabel}>
362
+ {item.label}
363
+ </Text>
364
+ <Icon
365
+ name={expandedItems[item.key] ? 'expand-less' : 'expand-more'}
366
+ size={20}
367
+ color="#64748B"
368
+ />
369
+ </View>
370
+ <Text style={styles.relationButtonMeta}>
371
+ {item.className?.split('\\').pop()} #{item.id}
372
+ </Text>
373
+ </TouchableOpacity>
374
+
375
+ {expandedItems[item.key] && (
376
+ <View
377
+ style={styles.nestedWrap}
378
+ onLayout={event =>
379
+ registerNestedOffset(item.key, event.nativeEvent.layout.y)
380
+ }>
381
+ <EntityLogBranch
382
+ autoLoad
383
+ entity={item.entity}
384
+ entityClass={item.className}
385
+ entityId={item.id}
386
+ entityIri={item.iri}
387
+ entityLabel={item.label}
388
+ fetchEntityDetails={fetchEntityDetails}
389
+ fetchLogs={fetchLogs}
390
+ getEntityState={getEntityState}
391
+ getLogsState={getLogsState}
392
+ onFocusBranch={onFocusBranch}
393
+ parentOffset={nestedOffsets[item.key] || branchOffset}
394
+ relationConfig={relationConfig}
395
+ styles={styles}
396
+ trailKeys={[...trailKeys, branchKey]}
397
+ />
398
+ </View>
399
+ )}
400
+ </>
401
+ ) : (
402
+ <View
403
+ style={styles.nestedWrap}
404
+ onLayout={event =>
405
+ registerNestedOffset(item.key, event.nativeEvent.layout.y)
406
+ }>
407
+ <EntityLogBranch
408
+ autoLoad
409
+ entity={item.entity}
410
+ entityClass={item.className}
411
+ entityId={item.id}
412
+ entityIri={item.iri}
413
+ entityLabel={item.label}
414
+ fetchEntityDetails={fetchEntityDetails}
415
+ fetchLogs={fetchLogs}
416
+ getEntityState={getEntityState}
417
+ getLogsState={getLogsState}
418
+ onFocusBranch={onFocusBranch}
419
+ parentOffset={nestedOffsets[item.key] || branchOffset}
420
+ relationConfig={relationConfig}
421
+ styles={styles}
422
+ trailKeys={[...trailKeys, branchKey]}
423
+ />
424
+ </View>
425
+ )}
426
+ </View>
427
+ ))}
428
+ </View>
429
+ )}
430
+ </View>
431
+ ))}
432
+ </View>
433
+ )}
434
+
435
+ <View style={styles.relationSection}>
436
+ <Text style={styles.sectionTitle}>Timeline</Text>
437
+
438
+ {logsState.status === 'loading' ? (
439
+ <View style={styles.stateBox}>
440
+ <ActivityIndicator size="small" color="#2563EB" />
441
+ <Text style={styles.stateText}>Carregando historico...</Text>
442
+ </View>
443
+ ) : null}
444
+
445
+ {logsState.status === 'error' ? (
446
+ <View style={styles.stateBox}>
447
+ <Icon name="error-outline" size={20} color="#DC2626" />
448
+ <Text style={styles.stateTitle}>Nao foi possivel carregar</Text>
449
+ <Text style={styles.stateText}>{logsState.error || 'Erro desconhecido'}</Text>
450
+ </View>
451
+ ) : null}
452
+
453
+ {logsState.status === 'success' && !logsState.items.length ? (
454
+ <View style={styles.stateBox}>
455
+ <Icon name="history-toggle-off" size={22} color="#94A3B8" />
456
+ <Text style={styles.stateTitle}>Nenhum log encontrado</Text>
457
+ <Text style={styles.stateText}>
458
+ Ainda nao existem registros para esta entidade.
459
+ </Text>
460
+ </View>
461
+ ) : null}
462
+
463
+ {logsState.status === 'success' && !!logsState.items.length ? (
464
+ <View style={styles.logList}>
465
+ {logsState.items.map(log => (
466
+ <LogCard
467
+ key={log?.id || `${branchKey}-${log?.createdAt}`}
468
+ columns={storeColumns}
469
+ log={log}
470
+ rowContext={resolvedEntity}
471
+ storeName={storeName}
472
+ styles={styles}
473
+ />
474
+ ))}
475
+ </View>
476
+ ) : null}
477
+ </View>
478
+ </View>
479
+ </View>
480
+ );
481
+ };
482
+