@controleonline/ui-common 1.2.70 → 1.2.71

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 (61) hide show
  1. package/package.json +31 -28
  2. package/src/api/index.js +237 -37
  3. package/src/react/components/AddImportModal.js +1 -1
  4. package/src/react/components/AnimatedModal.js +171 -0
  5. package/src/react/components/AnimatedModal.styles.js +21 -0
  6. package/src/react/components/AppTypeSwitcher.js +164 -0
  7. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  8. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  9. package/src/react/components/BottomNavigationBar.js +86 -31
  10. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  11. package/src/react/components/DefaultProvider.native.js +68 -66
  12. package/src/react/components/DefaultProvider.web.js +44 -42
  13. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  14. package/src/react/components/DeviceAlertSoundService.js +3 -3
  15. package/src/react/components/KioskModeBridge.js +2 -2
  16. package/src/react/components/LauncherModeBridge.js +2 -2
  17. package/src/react/components/ManagerPushBridge.native.js +2 -2
  18. package/src/react/components/RemoteCheckoutService.js +28 -20
  19. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  20. package/src/react/components/RuntimeInfoFooter.js +37 -9
  21. package/src/react/components/StateStore.js +258 -0
  22. package/src/react/components/WebsocketListener.native.js +11 -11
  23. package/src/react/config/deviceConfigBootstrap.js +66 -27
  24. package/src/react/css/orders.js +72 -0
  25. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  26. package/src/react/pages/SettingsPage/index.js +1266 -1167
  27. package/src/react/print/providers/local.js +1 -1
  28. package/src/react/router/publicRoutes.js +25 -0
  29. package/src/react/services/Cielo/Checkout.js +39 -0
  30. package/src/react/services/Cielo/Cielo.js +193 -0
  31. package/src/react/services/Cielo/Print.js +7 -0
  32. package/src/react/services/InfinitePay/Checkout.js +33 -0
  33. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  34. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  35. package/src/react/styles/global.js +115 -0
  36. package/src/react/utils/fileUrl.js +182 -16
  37. package/src/react/utils/menuNavigation.js +31 -0
  38. package/src/react/utils/orderIdentity.js +277 -0
  39. package/src/react/utils/remotePayment.js +115 -61
  40. package/src/react/utils/runtimeMenu.js +35 -3
  41. package/src/react/utils/shopConfig.js +50 -24
  42. package/src/react/utils/shopFranchises.js +56 -22
  43. package/src/react/utils/socketRuntimePipeline.js +14 -14
  44. package/src/store/configs/index.js +2 -2
  45. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  46. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  47. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  48. package/src/tests/react/services/Cielo.test.js +190 -0
  49. package/src/tests/react/utils/fileUrl.test.js +62 -0
  50. package/src/tests/react/utils/importStatus.test.js +2 -2
  51. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  52. package/src/tests/react/utils/remotePayment.test.js +62 -0
  53. package/src/tests/react/utils/runtimeFooter.test.js +4 -6
  54. package/src/tests/react/utils/runtimeLanguage.test.js +2 -2
  55. package/src/tests/react/utils/runtimeMenu.test.js +23 -3
  56. package/src/tests/react/utils/shopConfig.test.js +74 -0
  57. package/src/tests/react/utils/shopFranchises.test.js +79 -12
  58. package/src/tests/react/utils/translate.test.mjs +166 -102
  59. package/src/utils/formatter.js +18 -0
  60. package/src/utils/integrationConfigs.js +80 -79
  61. package/src/utils/translate.js +125 -30
@@ -1,462 +1,492 @@
1
- import React, {useCallback, useEffect, useMemo, useState} from 'react';
1
+ import React, {useCallback, useEffect, useMemo, useState} from 'react';
2
2
  import {
3
3
  Alert,
4
4
  StyleSheet,
5
5
  Text,
6
- TouchableOpacity,
7
- View,
8
- } from 'react-native';
9
-
6
+ TouchableOpacity,
7
+ View,
8
+ } from 'react-native';
9
+
10
+ import {api} from '@controleonline/ui-common/src/api';
10
11
  import {useStore} from '@store';
11
- import StateStore from '@controleonline/ui-layout/src/react/components/StateStore';
12
+ import StateStore from '@controleonline/ui-common/src/react/components/StateStore';
12
13
  import DefaultTooltip from '@controleonline/ui-default/src/react/components/help/DefaultTooltip';
13
14
  import {
14
15
  groupWalletPaymentTypesByWalletId,
15
- normalizeEntityId,
16
- resolveDevicePaymentTypeIds,
17
- } from '@controleonline/ui-common/src/react/utils/paymentDevices';
18
-
19
- const resolveWalletLabel = wallet => {
20
- return String(wallet?.wallet ?? '').trim();
21
- };
22
-
23
- const resolvePaymentTypeLabel = walletPaymentType => {
24
- const paymentType = walletPaymentType?.paymentType;
25
-
26
- if (!paymentType || typeof paymentType !== 'object') {
27
- return '';
28
- }
29
-
30
- return String(paymentType.paymentType ?? '').trim();
31
- };
32
-
33
- const localStyles = StyleSheet.create({
34
- container: {
35
- marginTop: 16,
36
- paddingBottom: 8,
37
- },
38
- introCard: {
39
- backgroundColor: '#EFF6FF',
40
- borderColor: '#BFDBFE',
41
- borderRadius: 12,
42
- borderWidth: 1,
43
- padding: 14,
44
- },
45
- introHeaderRow: {
46
- alignItems: 'center',
47
- flexDirection: 'row',
48
- gap: 10,
49
- justifyContent: 'space-between',
50
- },
51
- introTitle: {
52
- color: '#1E3A8A',
53
- fontSize: 16,
54
- fontWeight: '700',
55
- },
56
- emptyCard: {
57
- backgroundColor: '#F8FAFC',
58
- borderRadius: 12,
59
- marginTop: 14,
60
- padding: 14,
61
- },
62
- emptyTitle: {
63
- color: '#0F172A',
64
- fontSize: 14,
65
- fontWeight: '700',
66
- marginBottom: 4,
67
- },
68
- emptyText: {
69
- color: '#64748B',
70
- fontSize: 12,
71
- lineHeight: 18,
72
- },
73
- walletList: {
74
- marginTop: 12,
75
- gap: 12,
76
- },
77
- walletCard: {
78
- backgroundColor: '#FFFFFF',
79
- borderColor: '#E2E8F0',
80
- borderRadius: 14,
81
- borderWidth: 1,
82
- padding: 14,
83
- },
84
- walletCardHeader: {
85
- alignItems: 'center',
86
- flexDirection: 'row',
87
- justifyContent: 'space-between',
88
- },
89
- walletMeta: {
90
- flex: 1,
91
- minWidth: 0,
92
- },
93
- walletName: {
94
- color: '#0F172A',
95
- fontSize: 15,
96
- fontWeight: '700',
97
- },
98
- walletId: {
99
- color: '#64748B',
100
- fontSize: 12,
101
- marginTop: 2,
102
- },
103
- paymentTypesBlock: {
104
- marginTop: 12,
105
- },
106
- paymentTypesTitle: {
107
- color: '#334155',
108
- fontSize: 12,
109
- fontWeight: '700',
110
- marginBottom: 8,
111
- },
112
- paymentTypesRow: {
113
- flexDirection: 'row',
114
- flexWrap: 'wrap',
115
- gap: 8,
116
- },
117
- paymentTypeChip: {
118
- alignSelf: 'flex-start',
119
- backgroundColor: '#E0F2FE',
120
- borderColor: '#BAE6FD',
121
- borderRadius: 999,
122
- borderWidth: 1,
123
- paddingHorizontal: 10,
124
- paddingVertical: 8,
125
- },
126
- paymentTypeChipSelected: {
127
- backgroundColor: '#1B5587',
128
- borderColor: '#1B5587',
129
- },
130
- paymentTypeChipDisabled: {
131
- opacity: 0.55,
132
- },
133
- paymentTypeChipText: {
134
- color: '#0369A1',
135
- fontSize: 12,
136
- fontWeight: '700',
137
- },
138
- paymentTypeChipTextSelected: {
139
- color: '#FFFFFF',
140
- },
141
- paymentTypesEmpty: {
142
- color: '#64748B',
143
- fontSize: 12,
144
- lineHeight: 18,
145
- },
146
- footerHintRow: {
147
- marginTop: 12,
148
- },
149
- footerHint: {
150
- color: '#64748B',
151
- fontSize: 12,
152
- lineHeight: 18,
153
- },
154
- });
155
-
156
- const PaymentTypesByWalletTab = ({
157
- configs,
158
- currentCompanyId,
159
- disableSelection = false,
160
- isSaving = false,
161
- onPersistSelectedPaymentTypeIds,
162
- title = 'Pagamentos',
163
- introText = 'Os wallets organizam a lista, mas o que vale é a seleção dos meios de pagamento.',
164
- helpText = 'Esta seleção é a fonte usada pelo runtime para mostrar os pagamentos disponíveis neste contexto.',
165
- loadingWalletsText = 'Carregando wallets da empresa...',
166
- loadingPaymentsText = 'Carregando meios de pagamento...',
167
- emptyTitle = 'Nenhuma wallet encontrada',
168
- emptyText = 'Não há wallets carregadas para a empresa ativa.',
169
- emptyPaymentsText = 'Nenhum meio de pagamento vinculado a esta wallet.',
170
- unassignedTitle = 'Sem carteira',
171
- unassignedText = 'Meios de pagamento sem wallet vinculada.',
172
- }) => {
173
- const walletStore = useStore('wallet');
174
- const walletPaymentTypeStore = useStore('walletPaymentType');
175
- const {getters: walletGetters, actions: walletActions} = walletStore;
176
- const {
177
- getters: walletPaymentTypeGetters,
178
- actions: walletPaymentTypeActions,
179
- } = walletPaymentTypeStore;
180
-
181
- const wallets = Array.isArray(walletGetters.items) ? walletGetters.items : [];
182
- const walletPaymentTypes = Array.isArray(walletPaymentTypeGetters.items)
183
- ? walletPaymentTypeGetters.items
184
- : [];
185
- const walletsLoading = walletGetters.isLoading === true;
186
- const walletPaymentTypesLoading = walletPaymentTypeGetters.isLoading === true;
187
- const [selectedPaymentTypeIds, setSelectedPaymentTypeIds] = useState([]);
188
-
189
- useEffect(() => {
190
- if (!currentCompanyId) {
191
- walletActions.setItems([]);
192
- walletPaymentTypeActions.setItems([]);
193
- return;
194
- }
195
-
196
- walletActions
197
- .getItems({people: currentCompanyId})
198
- .catch(() => {
199
- walletActions.setItems([]);
200
- });
201
- walletPaymentTypeActions
202
- .getItems({'wallet.people': currentCompanyId})
203
- .catch(() => {
204
- walletPaymentTypeActions.setItems([]);
205
- });
206
- }, [currentCompanyId, walletActions, walletPaymentTypeActions]);
207
-
208
- useEffect(() => {
209
- const nextSelectedPaymentTypeIds = resolveDevicePaymentTypeIds(configs);
210
- setSelectedPaymentTypeIds(current => {
211
- const currentSignature = current
212
- .map(normalizeEntityId)
213
- .filter(Boolean)
214
- .join(',');
215
- const nextSignature = nextSelectedPaymentTypeIds
216
- .map(normalizeEntityId)
217
- .filter(Boolean)
218
- .join(',');
219
-
220
- return currentSignature === nextSignature
221
- ? current
222
- : nextSelectedPaymentTypeIds;
223
- });
224
- }, [configs]);
225
-
226
- const selectedPaymentTypeIdSet = useMemo(
227
- () => new Set(selectedPaymentTypeIds.map(normalizeEntityId).filter(Boolean)),
228
- [selectedPaymentTypeIds],
229
- );
230
-
231
- const walletGroups = useMemo(() => {
232
- const groupedPaymentTypes = groupWalletPaymentTypesByWalletId(
233
- walletPaymentTypes,
234
- );
235
- const groupedWalletIds = new Set();
236
-
237
- const groups = wallets
238
- .map(wallet => {
239
- const walletId = normalizeEntityId(wallet?.id);
240
-
241
- if (!walletId) {
242
- return null;
243
- }
244
-
245
- groupedWalletIds.add(walletId);
246
-
247
- return {
248
- key: walletId,
249
- walletId,
250
- wallet,
251
- paymentTypes: groupedPaymentTypes[walletId] ?? [],
252
- isUnassigned: false,
253
- };
254
- })
255
- .filter(Boolean);
256
-
257
- Object.entries(groupedPaymentTypes).forEach(([walletId, paymentTypes]) => {
258
- if (walletId === '__unassigned' || groupedWalletIds.has(walletId)) {
259
- return;
260
- }
261
-
262
- groups.push({
263
- key: `wallet-${walletId}`,
264
- walletId,
265
- wallet: paymentTypes[0]?.wallet ?? null,
266
- paymentTypes,
267
- isUnassigned: false,
268
- });
269
- });
270
-
271
- const unassignedPaymentTypes = groupedPaymentTypes.__unassigned ?? [];
272
- if (unassignedPaymentTypes.length > 0) {
273
- groups.push({
274
- key: '__unassigned',
275
- walletId: '',
276
- wallet: null,
277
- paymentTypes: unassignedPaymentTypes,
278
- isUnassigned: true,
279
- });
280
- }
281
-
282
- return groups;
283
- }, [walletPaymentTypes, wallets]);
284
-
285
- const persistSelectedPaymentTypeIds = useCallback(
286
- nextSelectedPaymentTypeIds => {
287
- if (typeof onPersistSelectedPaymentTypeIds !== 'function') {
288
- return Promise.resolve(nextSelectedPaymentTypeIds);
289
- }
290
-
291
- return Promise.resolve(onPersistSelectedPaymentTypeIds(nextSelectedPaymentTypeIds));
292
- },
293
- [onPersistSelectedPaymentTypeIds],
294
- );
295
-
296
- const handleTogglePaymentType = useCallback(
297
- paymentTypeId => {
298
- const normalizedPaymentTypeId = normalizeEntityId(paymentTypeId);
299
-
300
- if (!normalizedPaymentTypeId || isSaving || disableSelection) {
301
- return;
302
- }
303
-
304
- const currentIds = [
305
- ...new Set(
306
- selectedPaymentTypeIds.map(normalizeEntityId).filter(Boolean),
307
- ),
308
- ];
309
- const nextIdsSet = new Set(currentIds);
310
- const wasSelected = nextIdsSet.has(normalizedPaymentTypeId);
311
-
312
- if (wasSelected) {
313
- nextIdsSet.delete(normalizedPaymentTypeId);
314
- } else {
315
- nextIdsSet.add(normalizedPaymentTypeId);
316
- }
317
-
318
- const nextSelectedIds = currentIds.filter(id => nextIdsSet.has(id));
319
- if (!wasSelected) {
320
- nextSelectedIds.push(normalizedPaymentTypeId);
321
- }
322
-
323
- setSelectedPaymentTypeIds(nextSelectedIds);
324
- persistSelectedPaymentTypeIds(nextSelectedIds).catch(error => {
325
- Alert.alert(
326
- 'Erro ao gravar configurações',
327
- error?.message ?? JSON.stringify(error),
328
- );
329
- setSelectedPaymentTypeIds(resolveDevicePaymentTypeIds(configs));
330
- });
331
- },
332
- [
333
- configs,
334
- disableSelection,
335
- isSaving,
336
- selectedPaymentTypeIds,
337
- persistSelectedPaymentTypeIds,
338
- ],
339
- );
340
-
341
- const hasWallets = wallets.length > 0;
342
- const hasPaymentTypes = walletPaymentTypes.length > 0;
343
- const hasVisibleGroups = walletGroups.length > 0;
344
- const selectionEnabled = !isSaving && !disableSelection;
345
-
346
- return (
347
- <View style={localStyles.container}>
348
- <View style={localStyles.introCard}>
349
- <View style={localStyles.introHeaderRow}>
350
- <Text style={localStyles.introTitle}>{title}</Text>
351
- <DefaultTooltip
352
- title={title}
353
- message={`${introText} ${helpText}`.trim()}
354
- />
355
- </View>
356
- </View>
357
-
358
- {walletsLoading ? (
359
- <StateStore compact loading={loadingWalletsText} />
360
- ) : null}
361
-
362
- {!walletsLoading && walletPaymentTypesLoading ? (
363
- <StateStore compact loading={loadingPaymentsText} />
364
- ) : null}
365
-
366
- {!walletsLoading && !hasWallets && !hasPaymentTypes ? (
367
- <View style={localStyles.emptyCard}>
368
- <Text style={localStyles.emptyTitle}>{emptyTitle}</Text>
369
- <Text style={localStyles.emptyText}>{emptyText}</Text>
370
- </View>
371
- ) : null}
372
-
373
- {hasVisibleGroups ? (
374
- <View style={localStyles.walletList}>
375
- {walletGroups.map(group => {
376
- const walletLabel = group.isUnassigned
377
- ? unassignedTitle
378
- : resolveWalletLabel(group.wallet);
379
-
380
- return (
381
- <View key={group.key} style={localStyles.walletCard}>
382
- <View style={localStyles.walletCardHeader}>
383
- <View style={localStyles.walletMeta}>
384
- <Text style={localStyles.walletName}>{walletLabel}</Text>
385
- {!group.isUnassigned && (
386
- <Text style={localStyles.walletId}>
387
- {group.walletId ? `ID #${group.walletId}` : ''}
388
- </Text>
389
- )}
390
- </View>
391
- </View>
392
-
393
- <View style={localStyles.paymentTypesBlock}>
394
- <Text style={localStyles.paymentTypesTitle}>
395
- Meios de pagamento
396
- </Text>
397
- {group.paymentTypes.length > 0 ? (
398
- <View style={localStyles.paymentTypesRow}>
399
- {group.paymentTypes.map(item => {
400
- const walletPaymentTypeId = normalizeEntityId(item?.id);
401
- if (!walletPaymentTypeId) {
402
- return null;
403
- }
404
-
405
- const paymentTypeLabel = resolvePaymentTypeLabel(item);
406
- const selected = selectedPaymentTypeIdSet.has(
407
- walletPaymentTypeId,
408
- );
409
-
410
- if (!paymentTypeLabel) {
411
- return null;
412
- }
413
-
414
- return (
415
- <TouchableOpacity
416
- key={`${group.key}-${walletPaymentTypeId}`}
417
- activeOpacity={selectionEnabled ? 0.85 : 1}
418
- disabled={!selectionEnabled}
419
- onPress={() =>
420
- handleTogglePaymentType(walletPaymentTypeId)
421
- }
422
- style={[
423
- localStyles.paymentTypeChip,
424
- selected && localStyles.paymentTypeChipSelected,
425
- !selectionEnabled && localStyles.paymentTypeChipDisabled,
426
- ]}>
427
- <Text
428
- style={[
429
- localStyles.paymentTypeChipText,
430
- selected &&
431
- localStyles.paymentTypeChipTextSelected,
432
- ]}>
433
- {paymentTypeLabel}
434
- </Text>
435
- </TouchableOpacity>
436
- );
437
- })}
438
- </View>
439
- ) : (
440
- <Text style={localStyles.paymentTypesEmpty}>
441
- {group.isUnassigned ? unassignedText : emptyPaymentsText}
442
- </Text>
443
- )}
444
- </View>
445
- </View>
446
- );
447
- })}
448
- </View>
449
- ) : null}
450
-
451
- <View style={localStyles.footerHintRow}>
452
- <Text style={localStyles.footerHint}>
453
- {selectedPaymentTypeIds.length > 0
454
- ? `${selectedPaymentTypeIds.length} meio(s) de pagamento liberado(s).`
455
- : 'Nenhum meio de pagamento liberado ainda.'}
456
- </Text>
457
- </View>
458
- </View>
459
- );
460
- };
461
-
462
- export default PaymentTypesByWalletTab;
16
+ normalizeEntityId,
17
+ resolveDevicePaymentTypeIds,
18
+ } from '@controleonline/ui-common/src/react/utils/paymentDevices';
19
+
20
+ const resolveWalletLabel = wallet => {
21
+ return String(wallet?.wallet ?? '').trim();
22
+ };
23
+
24
+ const resolvePaymentTypeLabel = walletPaymentType => {
25
+ const paymentType = walletPaymentType?.paymentType;
26
+
27
+ if (!paymentType || typeof paymentType !== 'object') {
28
+ return '';
29
+ }
30
+
31
+ return String(paymentType.paymentType ?? '').trim();
32
+ };
33
+
34
+ const localStyles = StyleSheet.create({
35
+ container: {
36
+ marginTop: 16,
37
+ paddingBottom: 8,
38
+ },
39
+ introCard: {
40
+ backgroundColor: '#EFF6FF',
41
+ borderColor: '#BFDBFE',
42
+ borderRadius: 12,
43
+ borderWidth: 1,
44
+ padding: 14,
45
+ },
46
+ introHeaderRow: {
47
+ alignItems: 'center',
48
+ flexDirection: 'row',
49
+ gap: 10,
50
+ justifyContent: 'space-between',
51
+ },
52
+ introTitle: {
53
+ color: '#1E3A8A',
54
+ fontSize: 16,
55
+ fontWeight: '700',
56
+ },
57
+ emptyCard: {
58
+ backgroundColor: '#F8FAFC',
59
+ borderRadius: 12,
60
+ marginTop: 14,
61
+ padding: 14,
62
+ },
63
+ emptyTitle: {
64
+ color: '#0F172A',
65
+ fontSize: 14,
66
+ fontWeight: '700',
67
+ marginBottom: 4,
68
+ },
69
+ emptyText: {
70
+ color: '#64748B',
71
+ fontSize: 12,
72
+ lineHeight: 18,
73
+ },
74
+ walletList: {
75
+ marginTop: 12,
76
+ gap: 12,
77
+ },
78
+ walletCard: {
79
+ backgroundColor: '#FFFFFF',
80
+ borderColor: '#E2E8F0',
81
+ borderRadius: 14,
82
+ borderWidth: 1,
83
+ padding: 14,
84
+ },
85
+ walletCardHeader: {
86
+ alignItems: 'center',
87
+ flexDirection: 'row',
88
+ justifyContent: 'space-between',
89
+ },
90
+ walletMeta: {
91
+ flex: 1,
92
+ minWidth: 0,
93
+ },
94
+ walletName: {
95
+ color: '#0F172A',
96
+ fontSize: 15,
97
+ fontWeight: '700',
98
+ },
99
+ walletId: {
100
+ color: '#64748B',
101
+ fontSize: 12,
102
+ marginTop: 2,
103
+ },
104
+ paymentTypesBlock: {
105
+ marginTop: 12,
106
+ },
107
+ paymentTypesTitle: {
108
+ color: '#334155',
109
+ fontSize: 12,
110
+ fontWeight: '700',
111
+ marginBottom: 8,
112
+ },
113
+ paymentTypesRow: {
114
+ flexDirection: 'row',
115
+ flexWrap: 'wrap',
116
+ gap: 8,
117
+ },
118
+ paymentTypeChip: {
119
+ alignSelf: 'flex-start',
120
+ backgroundColor: '#E0F2FE',
121
+ borderColor: '#BAE6FD',
122
+ borderRadius: 999,
123
+ borderWidth: 1,
124
+ paddingHorizontal: 10,
125
+ paddingVertical: 8,
126
+ },
127
+ paymentTypeChipSelected: {
128
+ backgroundColor: '#1B5587',
129
+ borderColor: '#1B5587',
130
+ },
131
+ paymentTypeChipDisabled: {
132
+ opacity: 0.55,
133
+ },
134
+ paymentTypeChipText: {
135
+ color: '#0369A1',
136
+ fontSize: 12,
137
+ fontWeight: '700',
138
+ },
139
+ paymentTypeChipTextSelected: {
140
+ color: '#FFFFFF',
141
+ },
142
+ paymentTypesEmpty: {
143
+ color: '#64748B',
144
+ fontSize: 12,
145
+ lineHeight: 18,
146
+ },
147
+ footerHintRow: {
148
+ marginTop: 12,
149
+ },
150
+ footerHint: {
151
+ color: '#64748B',
152
+ fontSize: 12,
153
+ lineHeight: 18,
154
+ },
155
+ });
156
+
157
+ const PaymentTypesByWalletTab = ({
158
+ configs,
159
+ currentCompanyId,
160
+ disableSelection = false,
161
+ isSaving = false,
162
+ onPersistSelectedPaymentTypeIds,
163
+ title = 'Pagamentos',
164
+ introText = 'Os wallets organizam a lista, mas o que vale é a seleção dos meios de pagamento.',
165
+ helpText = 'Esta seleção é a fonte usada pelo runtime para mostrar os pagamentos disponíveis neste contexto.',
166
+ loadingWalletsText = 'Carregando wallets da empresa...',
167
+ loadingPaymentsText = 'Carregando meios de pagamento...',
168
+ emptyTitle = 'Nenhuma wallet encontrada',
169
+ emptyText = 'Não há wallets carregadas para a empresa ativa.',
170
+ emptyPaymentsText = 'Nenhum meio de pagamento vinculado a esta wallet.',
171
+ unassignedTitle = 'Sem carteira',
172
+ unassignedText = 'Meios de pagamento sem wallet vinculada.',
173
+ }) => {
174
+ const walletStore = useStore('wallet');
175
+ const {getters: walletGetters, actions: walletActions} = walletStore;
176
+
177
+ const wallets = Array.isArray(walletGetters.items) ? walletGetters.items : [];
178
+ const walletsLoading = walletGetters.isLoading === true;
179
+ const [selectedPaymentTypeIds, setSelectedPaymentTypeIds] = useState([]);
180
+ const [walletPaymentTypes, setWalletPaymentTypes] = useState([]);
181
+ const [walletPaymentTypesLoading, setWalletPaymentTypesLoading] =
182
+ useState(false);
183
+
184
+ useEffect(() => {
185
+ if (!currentCompanyId) {
186
+ walletActions.setItems([]);
187
+ setWalletPaymentTypes([]);
188
+ setWalletPaymentTypesLoading(false);
189
+ return;
190
+ }
191
+
192
+ let cancelled = false;
193
+
194
+ walletActions
195
+ .getItems({people: currentCompanyId})
196
+ .catch(() => {
197
+ walletActions.setItems([]);
198
+ });
199
+
200
+ setWalletPaymentTypesLoading(true);
201
+ api
202
+ .fetch('wallet_payment_types', {
203
+ params: {
204
+ 'wallet.people': currentCompanyId,
205
+ },
206
+ })
207
+ .then(response => {
208
+ if (cancelled) {
209
+ return;
210
+ }
211
+
212
+ const items = Array.isArray(response?.member)
213
+ ? response.member
214
+ : Array.isArray(response?.['hydra:member'])
215
+ ? response['hydra:member']
216
+ : Array.isArray(response)
217
+ ? response
218
+ : [];
219
+
220
+ setWalletPaymentTypes(items);
221
+ })
222
+ .catch(() => {
223
+ if (!cancelled) {
224
+ setWalletPaymentTypes([]);
225
+ }
226
+ })
227
+ .finally(() => {
228
+ if (!cancelled) {
229
+ setWalletPaymentTypesLoading(false);
230
+ }
231
+ });
232
+
233
+ return () => {
234
+ cancelled = true;
235
+ };
236
+ }, [currentCompanyId, walletActions]);
237
+
238
+ useEffect(() => {
239
+ const nextSelectedPaymentTypeIds = resolveDevicePaymentTypeIds(configs);
240
+ setSelectedPaymentTypeIds(current => {
241
+ const currentSignature = current
242
+ .map(normalizeEntityId)
243
+ .filter(Boolean)
244
+ .join(',');
245
+ const nextSignature = nextSelectedPaymentTypeIds
246
+ .map(normalizeEntityId)
247
+ .filter(Boolean)
248
+ .join(',');
249
+
250
+ return currentSignature === nextSignature
251
+ ? current
252
+ : nextSelectedPaymentTypeIds;
253
+ });
254
+ }, [configs]);
255
+
256
+ const selectedPaymentTypeIdSet = useMemo(
257
+ () => new Set(selectedPaymentTypeIds.map(normalizeEntityId).filter(Boolean)),
258
+ [selectedPaymentTypeIds],
259
+ );
260
+
261
+ const walletGroups = useMemo(() => {
262
+ const groupedPaymentTypes = groupWalletPaymentTypesByWalletId(
263
+ walletPaymentTypes,
264
+ );
265
+ const groupedWalletIds = new Set();
266
+
267
+ const groups = wallets
268
+ .map(wallet => {
269
+ const walletId = normalizeEntityId(wallet?.id);
270
+
271
+ if (!walletId) {
272
+ return null;
273
+ }
274
+
275
+ groupedWalletIds.add(walletId);
276
+
277
+ return {
278
+ key: walletId,
279
+ walletId,
280
+ wallet,
281
+ paymentTypes: groupedPaymentTypes[walletId] ?? [],
282
+ isUnassigned: false,
283
+ };
284
+ })
285
+ .filter(Boolean);
286
+
287
+ Object.entries(groupedPaymentTypes).forEach(([walletId, paymentTypes]) => {
288
+ if (walletId === '__unassigned' || groupedWalletIds.has(walletId)) {
289
+ return;
290
+ }
291
+
292
+ groups.push({
293
+ key: `wallet-${walletId}`,
294
+ walletId,
295
+ wallet: paymentTypes[0]?.wallet ?? null,
296
+ paymentTypes,
297
+ isUnassigned: false,
298
+ });
299
+ });
300
+
301
+ const unassignedPaymentTypes = groupedPaymentTypes.__unassigned ?? [];
302
+ if (unassignedPaymentTypes.length > 0) {
303
+ groups.push({
304
+ key: '__unassigned',
305
+ walletId: '',
306
+ wallet: null,
307
+ paymentTypes: unassignedPaymentTypes,
308
+ isUnassigned: true,
309
+ });
310
+ }
311
+
312
+ return groups;
313
+ }, [walletPaymentTypes, wallets]);
314
+
315
+ const persistSelectedPaymentTypeIds = useCallback(
316
+ nextSelectedPaymentTypeIds => {
317
+ if (typeof onPersistSelectedPaymentTypeIds !== 'function') {
318
+ return Promise.resolve(nextSelectedPaymentTypeIds);
319
+ }
320
+
321
+ return Promise.resolve(onPersistSelectedPaymentTypeIds(nextSelectedPaymentTypeIds));
322
+ },
323
+ [onPersistSelectedPaymentTypeIds],
324
+ );
325
+
326
+ const handleTogglePaymentType = useCallback(
327
+ paymentTypeId => {
328
+ const normalizedPaymentTypeId = normalizeEntityId(paymentTypeId);
329
+
330
+ if (!normalizedPaymentTypeId || isSaving || disableSelection) {
331
+ return;
332
+ }
333
+
334
+ const currentIds = [
335
+ ...new Set(
336
+ selectedPaymentTypeIds.map(normalizeEntityId).filter(Boolean),
337
+ ),
338
+ ];
339
+ const nextIdsSet = new Set(currentIds);
340
+ const wasSelected = nextIdsSet.has(normalizedPaymentTypeId);
341
+
342
+ if (wasSelected) {
343
+ nextIdsSet.delete(normalizedPaymentTypeId);
344
+ } else {
345
+ nextIdsSet.add(normalizedPaymentTypeId);
346
+ }
347
+
348
+ const nextSelectedIds = currentIds.filter(id => nextIdsSet.has(id));
349
+ if (!wasSelected) {
350
+ nextSelectedIds.push(normalizedPaymentTypeId);
351
+ }
352
+
353
+ setSelectedPaymentTypeIds(nextSelectedIds);
354
+ persistSelectedPaymentTypeIds(nextSelectedIds).catch(error => {
355
+ Alert.alert(
356
+ 'Erro ao gravar configurações',
357
+ error?.message ?? JSON.stringify(error),
358
+ );
359
+ setSelectedPaymentTypeIds(resolveDevicePaymentTypeIds(configs));
360
+ });
361
+ },
362
+ [
363
+ configs,
364
+ disableSelection,
365
+ isSaving,
366
+ selectedPaymentTypeIds,
367
+ persistSelectedPaymentTypeIds,
368
+ ],
369
+ );
370
+
371
+ const hasWallets = wallets.length > 0;
372
+ const hasPaymentTypes = walletPaymentTypes.length > 0;
373
+ const hasVisibleGroups = walletGroups.length > 0;
374
+ const selectionEnabled = !isSaving && !disableSelection;
375
+
376
+ return (
377
+ <View style={localStyles.container}>
378
+ <View style={localStyles.introCard}>
379
+ <View style={localStyles.introHeaderRow}>
380
+ <Text style={localStyles.introTitle}>{title}</Text>
381
+ <DefaultTooltip
382
+ title={title}
383
+ message={`${introText} ${helpText}`.trim()}
384
+ />
385
+ </View>
386
+ </View>
387
+
388
+ {walletsLoading ? (
389
+ <StateStore compact loading={loadingWalletsText} />
390
+ ) : null}
391
+
392
+ {!walletsLoading && walletPaymentTypesLoading ? (
393
+ <StateStore compact loading={loadingPaymentsText} />
394
+ ) : null}
395
+
396
+ {!walletsLoading && !hasWallets && !hasPaymentTypes ? (
397
+ <View style={localStyles.emptyCard}>
398
+ <Text style={localStyles.emptyTitle}>{emptyTitle}</Text>
399
+ <Text style={localStyles.emptyText}>{emptyText}</Text>
400
+ </View>
401
+ ) : null}
402
+
403
+ {hasVisibleGroups ? (
404
+ <View style={localStyles.walletList}>
405
+ {walletGroups.map(group => {
406
+ const walletLabel = group.isUnassigned
407
+ ? unassignedTitle
408
+ : resolveWalletLabel(group.wallet);
409
+
410
+ return (
411
+ <View key={group.key} style={localStyles.walletCard}>
412
+ <View style={localStyles.walletCardHeader}>
413
+ <View style={localStyles.walletMeta}>
414
+ <Text style={localStyles.walletName}>{walletLabel}</Text>
415
+ {!group.isUnassigned && (
416
+ <Text style={localStyles.walletId}>
417
+ {group.walletId ? `ID #${group.walletId}` : ''}
418
+ </Text>
419
+ )}
420
+ </View>
421
+ </View>
422
+
423
+ <View style={localStyles.paymentTypesBlock}>
424
+ <Text style={localStyles.paymentTypesTitle}>
425
+ Meios de pagamento
426
+ </Text>
427
+ {group.paymentTypes.length > 0 ? (
428
+ <View style={localStyles.paymentTypesRow}>
429
+ {group.paymentTypes.map(item => {
430
+ const walletPaymentTypeId = normalizeEntityId(item?.id);
431
+ if (!walletPaymentTypeId) {
432
+ return null;
433
+ }
434
+
435
+ const paymentTypeLabel = resolvePaymentTypeLabel(item);
436
+ const selected = selectedPaymentTypeIdSet.has(
437
+ walletPaymentTypeId,
438
+ );
439
+
440
+ if (!paymentTypeLabel) {
441
+ return null;
442
+ }
443
+
444
+ return (
445
+ <TouchableOpacity
446
+ key={`${group.key}-${walletPaymentTypeId}`}
447
+ activeOpacity={selectionEnabled ? 0.85 : 1}
448
+ disabled={!selectionEnabled}
449
+ onPress={() =>
450
+ handleTogglePaymentType(walletPaymentTypeId)
451
+ }
452
+ style={[
453
+ localStyles.paymentTypeChip,
454
+ selected && localStyles.paymentTypeChipSelected,
455
+ !selectionEnabled && localStyles.paymentTypeChipDisabled,
456
+ ]}>
457
+ <Text
458
+ style={[
459
+ localStyles.paymentTypeChipText,
460
+ selected &&
461
+ localStyles.paymentTypeChipTextSelected,
462
+ ]}>
463
+ {paymentTypeLabel}
464
+ </Text>
465
+ </TouchableOpacity>
466
+ );
467
+ })}
468
+ </View>
469
+ ) : (
470
+ <Text style={localStyles.paymentTypesEmpty}>
471
+ {group.isUnassigned ? unassignedText : emptyPaymentsText}
472
+ </Text>
473
+ )}
474
+ </View>
475
+ </View>
476
+ );
477
+ })}
478
+ </View>
479
+ ) : null}
480
+
481
+ <View style={localStyles.footerHintRow}>
482
+ <Text style={localStyles.footerHint}>
483
+ {selectedPaymentTypeIds.length > 0
484
+ ? `${selectedPaymentTypeIds.length} meio(s) de pagamento liberado(s).`
485
+ : 'Nenhum meio de pagamento liberado ainda.'}
486
+ </Text>
487
+ </View>
488
+ </View>
489
+ );
490
+ };
491
+
492
+ export default PaymentTypesByWalletTab;