@controleonline/ui-common 1.2.71 → 1.2.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/.github/actions/workers/manager/action.yml +147 -0
  2. package/.github/actions/workers/qa/action.yml +44 -0
  3. package/.github/actions/workers/security/action.yml +44 -0
  4. package/.github/actions/workers/technical-documenter/action.yml +51 -0
  5. package/.github/workflows/manager-worker.yml +168 -0
  6. package/.github/workflows/publish-npm.yml +45 -0
  7. package/.gitmodules +3 -0
  8. package/AGENTS.md +6 -0
  9. package/README.md +14 -0
  10. package/package.json +3 -2
  11. package/src/api/fetch.js +1 -1
  12. package/src/api/index.js +92 -22
  13. package/src/react/components/AddImportModal.js +94 -110
  14. package/src/react/components/BottomNavigationBar.config.js +211 -231
  15. package/src/react/components/CategoryForm.js +52 -35
  16. package/src/react/components/DefaultProvider.native.js +131 -95
  17. package/src/react/components/DefaultProvider.web.js +148 -74
  18. package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
  19. package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
  20. package/src/react/components/RuntimeInfoFooter.js +100 -36
  21. package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
  22. package/src/react/components/UnifiedPaymentBar.js +7 -4
  23. package/src/react/components/UserAvatar.js +160 -0
  24. package/src/react/components/integrations/IntegrationHero/index.js +48 -0
  25. package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
  26. package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
  27. package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
  28. package/src/react/config/deviceConfigBootstrap.js +5 -3
  29. package/src/react/pages/Connections.js +229 -0
  30. package/src/react/pages/Connections.styles.js +222 -0
  31. package/src/react/pages/DeviceDetailPage.js +2814 -0
  32. package/src/react/pages/DeviceDetailPage.styles.js +308 -0
  33. package/src/react/pages/Devices/currentDevice.js +194 -0
  34. package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
  35. package/src/react/pages/Devices/deviceTypes/all.js +23 -0
  36. package/src/react/pages/Devices/deviceTypes/device.js +25 -0
  37. package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
  38. package/src/react/pages/Devices/deviceTypes/display.js +26 -0
  39. package/src/react/pages/Devices/deviceTypes/index.js +145 -0
  40. package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
  41. package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
  42. package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
  43. package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
  44. package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
  45. package/src/react/pages/Devices.js +183 -0
  46. package/src/react/pages/Devices.styles.js +353 -0
  47. package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
  48. package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
  49. package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
  50. package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
  51. package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
  52. package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
  53. package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
  54. package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
  55. package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
  56. package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
  57. package/src/react/pages/Food99IntegrationPage.js +1 -0
  58. package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
  59. package/src/react/pages/GenericLogPage.js +42 -167
  60. package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
  61. package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
  62. package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
  63. package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
  64. package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
  65. package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
  66. package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
  67. package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
  68. package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
  69. package/src/react/pages/IFoodIntegrationPage.js +1 -0
  70. package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
  71. package/src/react/pages/Imports.js +214 -166
  72. package/src/react/pages/IntegrationConfigPage.js +744 -0
  73. package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
  74. package/src/react/pages/Integrations.js +299 -0
  75. package/src/react/pages/Integrations.styles.js +130 -0
  76. package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
  77. package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
  78. package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
  79. package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
  80. package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
  81. package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
  82. package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
  83. package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
  84. package/src/react/pages/PrinterDeviceFormPage.js +521 -0
  85. package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
  86. package/src/react/pages/WhatsAppConnectionPage.js +459 -0
  87. package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
  88. package/src/react/pages/integrationsCatalog.js +399 -0
  89. package/src/react/services/Cielo/Cielo.js +193 -193
  90. package/src/react/utils/categoryContexts.js +46 -0
  91. package/src/react/utils/entityDisplay.js +222 -217
  92. package/src/react/utils/fileUrl.js +39 -20
  93. package/src/react/utils/integrationPage.js +16 -0
  94. package/src/react/utils/runtimeFooter.js +40 -22
  95. package/src/react/utils/runtimeLanguage.js +50 -9
  96. package/src/react/utils/runtimeMenu.js +191 -131
  97. package/src/react/utils/runtimeZoom.js +43 -0
  98. package/src/react/utils/shopConfig.js +27 -15
  99. package/src/react/utils/shopFranchises.js +126 -1
  100. package/src/react/utils/storeColumns.js +23 -4
  101. package/src/react/utils/userAvatar.js +104 -0
  102. package/src/store/categories/index.js +48 -20
  103. package/src/store/device_config/index.js +62 -21
  104. package/src/store/entity_log/index.js +22 -1
  105. package/src/store/imports/customActions.js +51 -0
  106. package/src/store/imports/index.js +89 -88
  107. package/src/store/status/index.js +11 -11
  108. package/src/store/themes/index.js +22 -0
  109. package/src/store/timezones/index.js +3 -1
  110. package/src/tests/browser/manager/connections.spec.js +143 -0
  111. package/src/tests/browser/manager/devices-current.spec.js +343 -0
  112. package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
  113. package/src/tests/react/api/deviceHeaders.test.js +114 -0
  114. package/src/tests/react/components/AddImportModal.test.js +166 -0
  115. package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
  116. package/src/tests/react/components/UserAvatar.test.js +52 -0
  117. package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
  118. package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
  119. package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
  120. package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
  121. package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
  122. package/src/tests/react/services/Cielo.test.js +190 -190
  123. package/src/tests/react/store/categoriesOrdering.test.js +26 -0
  124. package/src/tests/react/utils/appDomain.test.js +62 -0
  125. package/src/tests/react/utils/entityDisplay.test.js +2 -2
  126. package/src/tests/react/utils/fileUrl.test.js +3 -3
  127. package/src/tests/react/utils/formatter.test.mjs +33 -0
  128. package/src/tests/react/utils/runtimeFooter.test.js +29 -4
  129. package/src/tests/react/utils/runtimeLanguage.test.js +79 -9
  130. package/src/tests/react/utils/runtimeMenu.test.js +198 -134
  131. package/src/tests/react/utils/runtimeZoom.test.js +38 -0
  132. package/src/tests/react/utils/shopConfig.test.js +10 -9
  133. package/src/tests/react/utils/shopFranchises.test.js +43 -0
  134. package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
  135. package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
  136. package/src/tests/react/utils/translate.test.mjs +141 -349
  137. package/src/tests/react/utils/userAvatar.test.js +63 -0
  138. package/src/utils/appDomain.js +15 -4
  139. package/src/utils/formatter.js +104 -16
  140. package/src/utils/integrationConfigs.js +80 -80
  141. package/src/utils/translate.js +222 -438
  142. package/src/utils/translateDiscovery.js +96 -0
  143. package/src/utils/translateNormalize.js +33 -0
  144. package/src/utils/translatePending.js +92 -0
  145. package/src/utils/translateStorage.js +142 -0
  146. package/.github/agents/developer.agent.md +0 -30
  147. package/.github/agents/devops.agent.md +0 -30
  148. package/.github/agents/qa.agent.md +0 -30
  149. package/.github/agents/security.agent.md +0 -30
  150. package/.github/workflows/npmjs.yml +0 -35
@@ -0,0 +1,459 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { ActivityIndicator, Platform, RefreshControl, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
3
+ import { SafeAreaView } from 'react-native-safe-area-context';
4
+ import { useFocusEffect } from '@react-navigation/native';
5
+ import Icon from 'react-native-vector-icons/Feather';
6
+ import QRCode from 'react-native-qrcode-svg';
7
+
8
+ import useToastMessage from '@controleonline/ui-crm/src/react/hooks/useToastMessage';
9
+ import { useStore } from '@store';
10
+ import { colors } from '@controleonline/../../src/styles/colors';
11
+ import styles from './WhatsAppConnectionPage.styles';
12
+ import {
13
+ resolveThemePalette,
14
+ withOpacity,
15
+ } from '@controleonline/../../src/styles/branding';
16
+
17
+ const shadowStyle = Platform.select({
18
+ ios: {
19
+ shadowColor: '#0F172A',
20
+ shadowOffset: { width: 0, height: 8 },
21
+ shadowOpacity: 0.08,
22
+ shadowRadius: 16,
23
+ },
24
+ android: { elevation: 3 },
25
+ web: { boxShadow: '0 10px 24px rgba(15,23,42,0.08)' },
26
+ });
27
+
28
+ const formatApiError = error => {
29
+ if (!error) return 'Nao foi possivel concluir a operacao.';
30
+ if (typeof error === 'string') return error;
31
+ return error?.message || error?.description || error?.errmsg || 'Nao foi possivel concluir a operacao.';
32
+ };
33
+
34
+ const toArray = response => {
35
+ if (Array.isArray(response)) return response;
36
+ if (Array.isArray(response?.member)) return response.member;
37
+ if (Array.isArray(response?.['hydra:member'])) return response['hydra:member'];
38
+ return [];
39
+ };
40
+
41
+ const extractDigits = value => String(value || '').replace(/\D/g, '');
42
+
43
+ const formatPhoneLabel = phone => {
44
+ const ddd = String(phone?.ddd || '').trim();
45
+ const digits = extractDigits(phone?.phone || '');
46
+ if (!ddd && !digits) return 'Nao informado';
47
+ if (!digits) return `(${ddd})`;
48
+ if (digits.length <= 4) return ddd ? `(${ddd}) ${digits}` : digits;
49
+
50
+ const lastFour = digits.slice(-4);
51
+ const firstPart = digits.slice(0, -4);
52
+ return ddd ? `(${ddd}) ${firstPart}-${lastFour}` : `${firstPart}-${lastFour}`;
53
+ };
54
+
55
+ const normalizeConnection = item => ({
56
+ id: item?.id || item?.['@id'] || `connection-${Math.random()}`,
57
+ name: item?.name || 'Sem nome',
58
+ status: item?.status?.status || item?.status || 'Pendente',
59
+ type: item?.type || 'Nenhum',
60
+ channel: item?.channel || 'whatsapp',
61
+ phone: item?.phone || {},
62
+ phoneLabel: formatPhoneLabel(item?.phone),
63
+ });
64
+
65
+ const sanitizeDdi = value => extractDigits(value).slice(0, 3);
66
+ const sanitizeDdd = value => extractDigits(value).slice(0, 2);
67
+ const sanitizePhone = value => extractDigits(value).slice(0, 9);
68
+
69
+ export default function WhatsAppConnectionPage() {
70
+ const peopleStore = useStore('people');
71
+ const themeStore = useStore('theme');
72
+ const connectionsStore = useStore('connections');
73
+ const { currentCompany } = peopleStore.getters;
74
+ const { colors: themeColors } = themeStore.getters;
75
+ const connectionsActions = connectionsStore.actions;
76
+ const { showError, showSuccess, showInfo } = useToastMessage();
77
+
78
+ const brandColors = useMemo(
79
+ () =>
80
+ resolveThemePalette(
81
+ {
82
+ ...themeColors,
83
+ ...(currentCompany?.theme?.colors || {}),
84
+ },
85
+ colors,
86
+ ),
87
+ [themeColors, currentCompany?.id],
88
+ );
89
+
90
+ const [loading, setLoading] = useState(true);
91
+ const [refreshing, setRefreshing] = useState(false);
92
+ const [submitting, setSubmitting] = useState(false);
93
+ const [connections, setConnections] = useState([]);
94
+ const [ddi, setDdi] = useState('55');
95
+ const [ddd, setDdd] = useState('');
96
+ const [phone, setPhone] = useState('');
97
+ const [qrValue, setQrValue] = useState('');
98
+ const [connectionStatus, setConnectionStatus] = useState('');
99
+ const [lastRequestedPhone, setLastRequestedPhone] = useState('');
100
+
101
+ const pollingRef = useRef(null);
102
+ const providerId = currentCompany?.id;
103
+ const peopleIri = useMemo(
104
+ () => (providerId ? `/people/${String(providerId).replace(/\D/g, '')}` : ''),
105
+ [providerId],
106
+ );
107
+
108
+ const completePhone = useMemo(() => `${ddi}${ddd}${phone}`, [ddi, ddd, phone]);
109
+
110
+ const clearPolling = useCallback(() => {
111
+ if (pollingRef.current) {
112
+ clearInterval(pollingRef.current);
113
+ pollingRef.current = null;
114
+ }
115
+ }, []);
116
+
117
+ const loadConnections = useCallback(async () => {
118
+ if (!peopleIri) {
119
+ setConnections([]);
120
+ setLoading(false);
121
+ return [];
122
+ }
123
+
124
+ try {
125
+ const response = await connectionsActions.getItems({
126
+ people: peopleIri,
127
+ });
128
+ const normalized = toArray(response).map(normalizeConnection);
129
+ setConnections(normalized);
130
+ return normalized;
131
+ } catch (error) {
132
+ showError(formatApiError(error));
133
+ setConnections([]);
134
+ return [];
135
+ } finally {
136
+ setLoading(false);
137
+ }
138
+ }, [connectionsActions, peopleIri, showError]);
139
+
140
+ const requestQrCode = useCallback(async phoneNumber => {
141
+ const targetPhone = extractDigits(phoneNumber);
142
+ if (!targetPhone) {
143
+ showError('Informe DDI, DDD e telefone para gerar o QR Code.');
144
+ return null;
145
+ }
146
+
147
+ try {
148
+ setSubmitting(true);
149
+ const response = await connectionsActions.createWhatsappConnection({
150
+ phone: targetPhone,
151
+ });
152
+
153
+ const nextStatus = String(response?.status || '').trim();
154
+ const nextQr = String(response?.qr || '').trim();
155
+
156
+ setLastRequestedPhone(targetPhone);
157
+ setConnectionStatus(nextStatus || 'Aguardando autenticacao');
158
+
159
+ if (nextQr) {
160
+ setQrValue(nextQr);
161
+ }
162
+
163
+ if (nextStatus === 'CONNECTED') {
164
+ clearPolling();
165
+ setQrValue('');
166
+ showSuccess('WhatsApp conectado com sucesso.');
167
+ await loadConnections();
168
+ }
169
+
170
+ return response;
171
+ } catch (error) {
172
+ clearPolling();
173
+ showError(formatApiError(error));
174
+ return null;
175
+ } finally {
176
+ setSubmitting(false);
177
+ }
178
+ }, [clearPolling, connectionsActions, loadConnections, showError, showSuccess]);
179
+
180
+ const startPolling = useCallback(async selectedPhone => {
181
+ const targetPhone = extractDigits(selectedPhone || completePhone);
182
+ if (!targetPhone) {
183
+ showError('Informe DDI, DDD e telefone para gerar o QR Code.');
184
+ return;
185
+ }
186
+
187
+ clearPolling();
188
+ const response = await requestQrCode(targetPhone);
189
+ if (response?.status === 'CONNECTED') {
190
+ return;
191
+ }
192
+
193
+ showInfo('Aguardando autenticacao do WhatsApp pelo QR Code.');
194
+ pollingRef.current = setInterval(() => {
195
+ requestQrCode(targetPhone);
196
+ }, 5000);
197
+ }, [clearPolling, completePhone, requestQrCode, showError, showInfo]);
198
+
199
+ useFocusEffect(
200
+ useCallback(() => {
201
+ setLoading(true);
202
+ loadConnections();
203
+
204
+ return () => {
205
+ clearPolling();
206
+ };
207
+ }, [clearPolling, loadConnections]),
208
+ );
209
+
210
+ useEffect(() => () => clearPolling(), [clearPolling]);
211
+
212
+ const onRefresh = useCallback(async () => {
213
+ setRefreshing(true);
214
+ try {
215
+ await loadConnections();
216
+ } finally {
217
+ setRefreshing(false);
218
+ }
219
+ }, [loadConnections]);
220
+
221
+ const handleUseExistingConnection = useCallback(item => {
222
+ const nextDdi = String(item?.phone?.ddi || '55');
223
+ const nextDdd = String(item?.phone?.ddd || '');
224
+ const nextPhone = extractDigits(item?.phone?.phone || '');
225
+
226
+ setDdi(nextDdi);
227
+ setDdd(nextDdd);
228
+ setPhone(nextPhone);
229
+ setConnectionStatus(item?.status || 'Pendente');
230
+ setLastRequestedPhone(`${nextDdi}${nextDdd}${nextPhone}`);
231
+ setQrValue('');
232
+ }, []);
233
+
234
+ if (!providerId) {
235
+ return (
236
+ <SafeAreaView style={styles.container} edges={['bottom']}>
237
+ <View style={styles.centerState}>
238
+ <Icon name="building" size={32} color="#94A3B8" />
239
+ <Text style={styles.centerStateTitle}>Selecione uma empresa</Text>
240
+ <Text style={styles.centerStateText}>
241
+ A configuracao do WhatsApp depende da empresa ativa.
242
+ </Text>
243
+ </View>
244
+ </SafeAreaView>
245
+ );
246
+ }
247
+
248
+ if (loading) {
249
+ return (
250
+ <SafeAreaView style={[styles.container, { backgroundColor: brandColors.background }]} edges={['bottom']}>
251
+ <View style={styles.centerState}>
252
+ <ActivityIndicator size="large" color={brandColors.primary} />
253
+ <Text style={styles.centerStateTitle}>Carregando canal</Text>
254
+ <Text style={styles.centerStateText}>
255
+ Buscando conexoes de WhatsApp da empresa ativa.
256
+ </Text>
257
+ </View>
258
+ </SafeAreaView>
259
+ );
260
+ }
261
+
262
+ return (
263
+ <SafeAreaView style={[styles.container, { backgroundColor: brandColors.background }]} edges={['bottom']}>
264
+ <ScrollView
265
+ contentContainerStyle={styles.scroll}
266
+ showsVerticalScrollIndicator={false}
267
+ refreshControl={
268
+ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={brandColors.primary} />
269
+ }>
270
+ <View style={[styles.heroCard, shadowStyle, { backgroundColor: brandColors.primary }]}>
271
+ <View style={styles.heroCopy}>
272
+ <Text style={styles.heroEyebrow}>CANAIS DE COMUNICACAO</Text>
273
+ <Text style={styles.heroTitle}>WhatsApp</Text>
274
+ <Text style={styles.heroText}>
275
+ Gere o QR Code, acompanhe o status e gerencie os numeros conectados.
276
+ </Text>
277
+ </View>
278
+ <View style={styles.heroBadge}>
279
+ <Icon name="message-circle" size={22} color={brandColors.primary} />
280
+ </View>
281
+ </View>
282
+
283
+ <View style={[styles.formCard, shadowStyle]}>
284
+ <View style={styles.formHeader}>
285
+ <View>
286
+ <Text style={styles.cardTitle}>Nova autenticacao</Text>
287
+ <Text style={styles.cardSubtitle}>
288
+ Informe o numero e solicite um novo QR Code de acesso.
289
+ </Text>
290
+ </View>
291
+ <View style={[styles.statusBadge, { backgroundColor: withOpacity('#2563EB', 0.12) }]}>
292
+ <Text style={styles.statusBadgeText}>{connectionStatus || 'Aguardando configuracao'}</Text>
293
+ </View>
294
+ </View>
295
+
296
+ <View style={styles.formRow}>
297
+ <View style={styles.inputGroupSmall}>
298
+ <Text style={styles.inputLabel}>DDI</Text>
299
+ <TextInput
300
+ keyboardType="number-pad"
301
+ onChangeText={value => setDdi(sanitizeDdi(value))}
302
+ placeholder="55"
303
+ placeholderTextColor="#94A3B8"
304
+ style={styles.input}
305
+ value={ddi}
306
+ />
307
+ </View>
308
+ <View style={styles.inputGroupSmall}>
309
+ <Text style={styles.inputLabel}>DDD</Text>
310
+ <TextInput
311
+ keyboardType="number-pad"
312
+ onChangeText={value => setDdd(sanitizeDdd(value))}
313
+ placeholder="11"
314
+ placeholderTextColor="#94A3B8"
315
+ style={styles.input}
316
+ value={ddd}
317
+ />
318
+ </View>
319
+ <View style={styles.inputGroupLarge}>
320
+ <Text style={styles.inputLabel}>Telefone</Text>
321
+ <TextInput
322
+ keyboardType="number-pad"
323
+ onChangeText={value => setPhone(sanitizePhone(value))}
324
+ placeholder="999999999"
325
+ placeholderTextColor="#94A3B8"
326
+ style={styles.input}
327
+ value={phone}
328
+ />
329
+ </View>
330
+ </View>
331
+
332
+ <TouchableOpacity
333
+ activeOpacity={0.9}
334
+ disabled={submitting}
335
+ onPress={() => startPolling(completePhone)}
336
+ style={[
337
+ styles.primaryButton,
338
+ { backgroundColor: brandColors.primary },
339
+ submitting && styles.primaryButtonDisabled,
340
+ ]}>
341
+ {submitting ? (
342
+ <ActivityIndicator color="#FFFFFF" />
343
+ ) : (
344
+ <>
345
+ <Icon name="smartphone" size={18} color="#FFFFFF" />
346
+ <Text style={styles.primaryButtonText}>Solicitar QR Code</Text>
347
+ </>
348
+ )}
349
+ </TouchableOpacity>
350
+
351
+ <Text style={styles.helperText}>
352
+ Numero atual: {completePhone || 'Nao informado'}
353
+ </Text>
354
+ </View>
355
+
356
+ <View style={[styles.qrCard, shadowStyle]}>
357
+ <View style={styles.qrHeader}>
358
+ <View>
359
+ <Text style={styles.cardTitle}>QR Code</Text>
360
+ <Text style={styles.cardSubtitle}>
361
+ Escaneie com o WhatsApp para concluir a autenticacao do canal.
362
+ </Text>
363
+ </View>
364
+ </View>
365
+
366
+ {qrValue ? (
367
+ <View style={styles.qrContent}>
368
+ <View style={styles.qrWrap}>
369
+ <QRCode value={qrValue} size={210} />
370
+ </View>
371
+ <Text style={styles.qrCaption}>
372
+ Atualizando automaticamente a cada 5 segundos enquanto a autenticacao estiver pendente.
373
+ </Text>
374
+ </View>
375
+ ) : (
376
+ <View style={styles.emptyStateBox}>
377
+ <Icon name="maximize" size={24} color="#94A3B8" />
378
+ <Text style={styles.emptyStateTitle}>QR Code indisponivel</Text>
379
+ <Text style={styles.emptyStateText}>
380
+ Solicite um QR Code para o numero escolhido ou selecione uma conexao existente abaixo.
381
+ </Text>
382
+ </View>
383
+ )}
384
+ </View>
385
+
386
+ <View style={styles.listHeader}>
387
+ <View>
388
+ <Text style={styles.cardTitle}>Conexoes existentes</Text>
389
+ <Text style={styles.cardSubtitle}>
390
+ Toque em um item para reutilizar o numero e solicitar um novo QR Code.
391
+ </Text>
392
+ </View>
393
+ <View style={styles.countBadge}>
394
+ <Text style={styles.countBadgeText}>{connections.length} item(ns)</Text>
395
+ </View>
396
+ </View>
397
+
398
+ <View style={styles.listWrap}>
399
+ {connections.length > 0 ? (
400
+ connections.map(item => (
401
+ <TouchableOpacity
402
+ key={item.id}
403
+ activeOpacity={0.92}
404
+ onPress={() => handleUseExistingConnection(item)}
405
+ style={[styles.connectionCard, shadowStyle]}>
406
+ <View style={styles.connectionTop}>
407
+ <View style={[styles.connectionIconWrap, { backgroundColor: withOpacity('#10b981', 0.12) }]}>
408
+ <Icon name="phone-call" size={18} color="#10b981" />
409
+ </View>
410
+ <View style={[styles.connectionStatusBadge, { backgroundColor: withOpacity('#2563EB', 0.12) }]}>
411
+ <Text style={styles.connectionStatusText}>{item.status}</Text>
412
+ </View>
413
+ </View>
414
+
415
+ <Text style={styles.connectionName}>{item.name}</Text>
416
+
417
+ <View style={styles.connectionMetaRow}>
418
+ <View style={styles.connectionMetaItem}>
419
+ <Text style={styles.connectionMetaLabel}>Telefone</Text>
420
+ <Text style={styles.connectionMetaValue}>{item.phoneLabel}</Text>
421
+ </View>
422
+ <View style={styles.connectionMetaItem}>
423
+ <Text style={styles.connectionMetaLabel}>Tipo</Text>
424
+ <Text style={styles.connectionMetaValue}>{item.type}</Text>
425
+ </View>
426
+ </View>
427
+
428
+ <View style={styles.connectionMetaRow}>
429
+ <View style={styles.connectionMetaItem}>
430
+ <Text style={styles.connectionMetaLabel}>Canal</Text>
431
+ <Text style={styles.connectionMetaValue}>{item.channel}</Text>
432
+ </View>
433
+ <View style={styles.connectionMetaItem}>
434
+ <Text style={styles.connectionMetaLabel}>Ultima sessao</Text>
435
+ <Text style={styles.connectionMetaValue}>
436
+ {extractDigits(item.phone?.phone || '') &&
437
+ lastRequestedPhone &&
438
+ lastRequestedPhone.endsWith(extractDigits(item.phone?.phone || ''))
439
+ ? 'Selecionada'
440
+ : 'Disponivel'}
441
+ </Text>
442
+ </View>
443
+ </View>
444
+ </TouchableOpacity>
445
+ ))
446
+ ) : (
447
+ <View style={[styles.emptyStateBox, { marginTop: 4 }]}>
448
+ <Icon name="inbox" size={24} color="#94A3B8" />
449
+ <Text style={styles.emptyStateTitle}>Nenhuma conexao cadastrada</Text>
450
+ <Text style={styles.emptyStateText}>
451
+ O primeiro QR Code gerado vai iniciar a configuracao deste canal para a empresa.
452
+ </Text>
453
+ </View>
454
+ )}
455
+ </View>
456
+ </ScrollView>
457
+ </SafeAreaView>
458
+ );
459
+ }