@controleonline/ui-common 1.2.70 → 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 (188) 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 +32 -28
  11. package/src/api/fetch.js +1 -1
  12. package/src/api/index.js +328 -58
  13. package/src/react/components/AddImportModal.js +95 -111
  14. package/src/react/components/AnimatedModal.js +171 -0
  15. package/src/react/components/AnimatedModal.styles.js +21 -0
  16. package/src/react/components/AppTypeSwitcher.js +164 -0
  17. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  18. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  19. package/src/react/components/BottomNavigationBar.config.js +211 -231
  20. package/src/react/components/BottomNavigationBar.js +86 -31
  21. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  22. package/src/react/components/CategoryForm.js +52 -35
  23. package/src/react/components/DefaultProvider.native.js +194 -156
  24. package/src/react/components/DefaultProvider.web.js +188 -112
  25. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  26. package/src/react/components/DeviceAlertSoundService.js +3 -3
  27. package/src/react/components/KioskModeBridge.js +2 -2
  28. package/src/react/components/LauncherModeBridge.js +2 -2
  29. package/src/react/components/ManagerPushBridge.native.js +2 -2
  30. package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
  31. package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
  32. package/src/react/components/RemoteCheckoutService.js +28 -20
  33. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  34. package/src/react/components/RuntimeInfoFooter.js +137 -45
  35. package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
  36. package/src/react/components/StateStore.js +258 -0
  37. package/src/react/components/UnifiedPaymentBar.js +7 -4
  38. package/src/react/components/UserAvatar.js +160 -0
  39. package/src/react/components/WebsocketListener.native.js +11 -11
  40. package/src/react/components/integrations/IntegrationHero/index.js +48 -0
  41. package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
  42. package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
  43. package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
  44. package/src/react/config/deviceConfigBootstrap.js +71 -30
  45. package/src/react/css/orders.js +72 -0
  46. package/src/react/pages/Connections.js +229 -0
  47. package/src/react/pages/Connections.styles.js +222 -0
  48. package/src/react/pages/DeviceDetailPage.js +2814 -0
  49. package/src/react/pages/DeviceDetailPage.styles.js +308 -0
  50. package/src/react/pages/Devices/currentDevice.js +194 -0
  51. package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
  52. package/src/react/pages/Devices/deviceTypes/all.js +23 -0
  53. package/src/react/pages/Devices/deviceTypes/device.js +25 -0
  54. package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
  55. package/src/react/pages/Devices/deviceTypes/display.js +26 -0
  56. package/src/react/pages/Devices/deviceTypes/index.js +145 -0
  57. package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
  58. package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
  59. package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
  60. package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
  61. package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
  62. package/src/react/pages/Devices.js +183 -0
  63. package/src/react/pages/Devices.styles.js +353 -0
  64. package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
  65. package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
  66. package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
  67. package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
  68. package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
  69. package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
  70. package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
  71. package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
  72. package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
  73. package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
  74. package/src/react/pages/Food99IntegrationPage.js +1 -0
  75. package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
  76. package/src/react/pages/GenericLogPage.js +42 -167
  77. package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
  78. package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
  79. package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
  80. package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
  81. package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
  82. package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
  83. package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
  84. package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
  85. package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
  86. package/src/react/pages/IFoodIntegrationPage.js +1 -0
  87. package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
  88. package/src/react/pages/Imports.js +214 -166
  89. package/src/react/pages/IntegrationConfigPage.js +744 -0
  90. package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
  91. package/src/react/pages/Integrations.js +299 -0
  92. package/src/react/pages/Integrations.styles.js +130 -0
  93. package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
  94. package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
  95. package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
  96. package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
  97. package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
  98. package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
  99. package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
  100. package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
  101. package/src/react/pages/PrinterDeviceFormPage.js +521 -0
  102. package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
  103. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  104. package/src/react/pages/SettingsPage/index.js +1266 -1167
  105. package/src/react/pages/WhatsAppConnectionPage.js +459 -0
  106. package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
  107. package/src/react/pages/integrationsCatalog.js +399 -0
  108. package/src/react/print/providers/local.js +1 -1
  109. package/src/react/router/publicRoutes.js +25 -0
  110. package/src/react/services/Cielo/Checkout.js +39 -0
  111. package/src/react/services/Cielo/Cielo.js +193 -0
  112. package/src/react/services/Cielo/Print.js +7 -0
  113. package/src/react/services/InfinitePay/Checkout.js +33 -0
  114. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  115. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  116. package/src/react/styles/global.js +115 -0
  117. package/src/react/utils/categoryContexts.js +46 -0
  118. package/src/react/utils/entityDisplay.js +222 -217
  119. package/src/react/utils/fileUrl.js +201 -16
  120. package/src/react/utils/integrationPage.js +16 -0
  121. package/src/react/utils/menuNavigation.js +31 -0
  122. package/src/react/utils/orderIdentity.js +277 -0
  123. package/src/react/utils/remotePayment.js +115 -61
  124. package/src/react/utils/runtimeFooter.js +40 -22
  125. package/src/react/utils/runtimeLanguage.js +50 -9
  126. package/src/react/utils/runtimeMenu.js +190 -98
  127. package/src/react/utils/runtimeZoom.js +43 -0
  128. package/src/react/utils/shopConfig.js +72 -34
  129. package/src/react/utils/shopFranchises.js +182 -23
  130. package/src/react/utils/socketRuntimePipeline.js +14 -14
  131. package/src/react/utils/storeColumns.js +23 -4
  132. package/src/react/utils/userAvatar.js +104 -0
  133. package/src/store/categories/index.js +48 -20
  134. package/src/store/configs/index.js +2 -2
  135. package/src/store/device_config/index.js +62 -21
  136. package/src/store/entity_log/index.js +22 -1
  137. package/src/store/imports/customActions.js +51 -0
  138. package/src/store/imports/index.js +89 -88
  139. package/src/store/status/index.js +11 -11
  140. package/src/store/themes/index.js +22 -0
  141. package/src/store/timezones/index.js +3 -1
  142. package/src/tests/browser/manager/connections.spec.js +143 -0
  143. package/src/tests/browser/manager/devices-current.spec.js +343 -0
  144. package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
  145. package/src/tests/react/api/deviceHeaders.test.js +114 -0
  146. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  147. package/src/tests/react/components/AddImportModal.test.js +166 -0
  148. package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
  149. package/src/tests/react/components/UserAvatar.test.js +52 -0
  150. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  151. package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
  152. package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
  153. package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
  154. package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
  155. package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
  156. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  157. package/src/tests/react/services/Cielo.test.js +190 -0
  158. package/src/tests/react/store/categoriesOrdering.test.js +26 -0
  159. package/src/tests/react/utils/appDomain.test.js +62 -0
  160. package/src/tests/react/utils/entityDisplay.test.js +2 -2
  161. package/src/tests/react/utils/fileUrl.test.js +62 -0
  162. package/src/tests/react/utils/formatter.test.mjs +33 -0
  163. package/src/tests/react/utils/importStatus.test.js +2 -2
  164. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  165. package/src/tests/react/utils/remotePayment.test.js +62 -0
  166. package/src/tests/react/utils/runtimeFooter.test.js +32 -9
  167. package/src/tests/react/utils/runtimeLanguage.test.js +80 -10
  168. package/src/tests/react/utils/runtimeMenu.test.js +195 -111
  169. package/src/tests/react/utils/runtimeZoom.test.js +38 -0
  170. package/src/tests/react/utils/shopConfig.test.js +75 -0
  171. package/src/tests/react/utils/shopFranchises.test.js +122 -12
  172. package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
  173. package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
  174. package/src/tests/react/utils/translate.test.mjs +252 -396
  175. package/src/tests/react/utils/userAvatar.test.js +63 -0
  176. package/src/utils/appDomain.js +15 -4
  177. package/src/utils/formatter.js +122 -16
  178. package/src/utils/integrationConfigs.js +1 -0
  179. package/src/utils/translate.js +277 -398
  180. package/src/utils/translateDiscovery.js +96 -0
  181. package/src/utils/translateNormalize.js +33 -0
  182. package/src/utils/translatePending.js +92 -0
  183. package/src/utils/translateStorage.js +142 -0
  184. package/.github/agents/developer.agent.md +0 -30
  185. package/.github/agents/devops.agent.md +0 -30
  186. package/.github/agents/qa.agent.md +0 -30
  187. package/.github/agents/security.agent.md +0 -30
  188. package/.github/workflows/npmjs.yml +0 -35
@@ -0,0 +1,420 @@
1
+ import React from 'react';
2
+ import { ActivityIndicator, Text, TextInput, TouchableOpacity, View } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { colors } from '@controleonline/../../src/styles/colors';
5
+
6
+ import { withOpacity } from '@controleonline/../../src/styles/branding';
7
+
8
+ import styles from '../styles';
9
+ import { calcEndTime, DAY_LABELS, DAY_ORDER, formatDateTimeLabel } from '../utils';
10
+
11
+ // Aba com operação diária: disponibilidade e horários da loja.
12
+ export default function IFoodOperationsTab({
13
+ shadowStyle,
14
+ connected,
15
+ storeStatus,
16
+ activeInterruptions,
17
+ actionLoading,
18
+ interruptionDraft,
19
+ interruptionRemoving,
20
+ onInterruptionDraftChange,
21
+ onRemoveInterruption,
22
+ onStoreOpen,
23
+ onStoreClose,
24
+ onRefreshStatus,
25
+ hours,
26
+ hoursLoading,
27
+ hoursEditing,
28
+ onStartEditHours,
29
+ hoursDraft,
30
+ onUpdateHoursDraft,
31
+ onAddHoursShift,
32
+ onRemoveHoursShift,
33
+ hoursSaving,
34
+ onSaveHours,
35
+ onCancelEditHours,
36
+ palette = colors,
37
+ }) {
38
+ if (!connected) {
39
+ return (
40
+ <View style={[styles.sectionCard, shadowStyle]}>
41
+ <Text style={styles.sectionTitle}>Operacao da loja</Text>
42
+ <View style={styles.helperRow}>
43
+ <Text style={styles.helperText}>
44
+ Conecte a loja primeiro para consultar disponibilidade, pausas ativas e horários do iFood.
45
+ </Text>
46
+ </View>
47
+ </View>
48
+ );
49
+ }
50
+
51
+ return (
52
+ <>
53
+ <View style={[styles.sectionCard, shadowStyle]}>
54
+ <View style={styles.statusRow}>
55
+ <Text style={styles.sectionTitle}>Disponibilidade da loja</Text>
56
+ {storeStatus?.data != null && (
57
+ <View
58
+ style={[
59
+ styles.availBadge,
60
+ {
61
+ backgroundColor: withOpacity(storeStatus.data.online ? palette.success : palette.error, 0.12),
62
+ },
63
+ ]}>
64
+ <View
65
+ style={[
66
+ styles.availDot,
67
+ { backgroundColor: storeStatus.data.online ? palette.success : palette.error },
68
+ ]}
69
+ />
70
+ <Text
71
+ style={[
72
+ styles.availBadgeText,
73
+ { color: storeStatus.data.online ? palette.success : palette.error },
74
+ ]}>
75
+ {storeStatus.data.online ? 'Online' : 'Offline'}
76
+ </Text>
77
+ </View>
78
+ )}
79
+ </View>
80
+
81
+ <Text style={styles.helperText}>
82
+ {storeStatus?.data != null
83
+ ? storeStatus.data.online
84
+ ? 'A loja esta aceitando pedidos no iFood.'
85
+ : `A loja esta fechada para novos pedidos.${activeInterruptions.length > 0 ? ` ${activeInterruptions.length} interrupcao(oes) ativa(s).` : ''}`
86
+ : 'Consulte ou altere a disponibilidade da loja para receber pedidos.'}
87
+ </Text>
88
+
89
+ {activeInterruptions.length > 0 && (
90
+ <View style={styles.interruptionsBox}>
91
+ <Text style={styles.interruptionsTitle}>Pausas ativas</Text>
92
+ {activeInterruptions.map((interruption, index) => (
93
+ <View key={String(interruption?.id || index)} style={styles.interruptionItem}>
94
+ <View style={styles.interruptionHeader}>
95
+ <Text style={styles.interruptionText}>
96
+ {interruption?.description || 'Interrupcao ativa'}
97
+ </Text>
98
+ <TouchableOpacity
99
+ activeOpacity={0.9}
100
+ style={styles.interruptionRemoveButton}
101
+ onPress={() => onRemoveInterruption(interruption)}
102
+ disabled={actionLoading !== null || interruptionRemoving?.has?.(String(interruption?.id || ''))}>
103
+ {interruptionRemoving?.has?.(String(interruption?.id || '')) ? (
104
+ <ActivityIndicator size="small" color={palette.error} />
105
+ ) : (
106
+ <Icon name="trash-2" size={13} color={palette.error} />
107
+ )}
108
+ </TouchableOpacity>
109
+ </View>
110
+ <Text style={styles.interruptionMeta}>
111
+ Inicio: {formatDateTimeLabel(interruption?.start)}
112
+ </Text>
113
+ <Text style={styles.interruptionMeta}>
114
+ Fim: {formatDateTimeLabel(interruption?.end)}
115
+ </Text>
116
+ </View>
117
+ ))}
118
+ </View>
119
+ )}
120
+
121
+ <View style={styles.interruptionForm}>
122
+ <Text style={styles.interruptionsTitle}>Nova pausa</Text>
123
+ <TextInput
124
+ value={interruptionDraft?.description || ''}
125
+ onChangeText={value => onInterruptionDraftChange('description', value)}
126
+ style={styles.input}
127
+ placeholder="Motivo da pausa"
128
+ placeholderTextColor={palette.textSecondary}
129
+ editable={actionLoading === null}
130
+ />
131
+ <View style={styles.interruptionDateRow}>
132
+ <View style={styles.interruptionDateField}>
133
+ <Text style={styles.inputLabel}>Inicio</Text>
134
+ <TextInput
135
+ value={interruptionDraft?.start || ''}
136
+ onChangeText={value => onInterruptionDraftChange('start', value)}
137
+ style={styles.input}
138
+ placeholder="AAAA-MM-DDTHH:MM"
139
+ placeholderTextColor={palette.textSecondary}
140
+ keyboardType="numbers-and-punctuation"
141
+ editable={actionLoading === null}
142
+ />
143
+ </View>
144
+ <View style={styles.interruptionDateField}>
145
+ <Text style={styles.inputLabel}>Fim</Text>
146
+ <TextInput
147
+ value={interruptionDraft?.end || ''}
148
+ onChangeText={value => onInterruptionDraftChange('end', value)}
149
+ style={styles.input}
150
+ placeholder="AAAA-MM-DDTHH:MM"
151
+ placeholderTextColor={palette.textSecondary}
152
+ keyboardType="numbers-and-punctuation"
153
+ editable={actionLoading === null}
154
+ />
155
+ </View>
156
+ </View>
157
+ <Text style={styles.interruptionMeta}>
158
+ Formato: AAAA-MM-DDTHH:MM. O iFood limita pausas a no maximo 7 dias e nao permite sobreposicao.
159
+ </Text>
160
+ </View>
161
+
162
+ {Array.isArray(storeStatus?.data?.operations) && (() => {
163
+ const storeOnline = storeStatus.data.online === true;
164
+ const visibleOperations = storeStatus.data.operations.filter(
165
+ operation => storeOnline || operation.state !== 'ERROR',
166
+ );
167
+
168
+ if (visibleOperations.length === 0) return null;
169
+
170
+ return (
171
+ <View style={styles.opsGrid}>
172
+ {visibleOperations.map((operation, index) => {
173
+ const online = operation.state === 'OK' || operation.state === 'WARNING';
174
+ const tone =
175
+ operation.state === 'OK'
176
+ ? palette.success
177
+ : operation.state === 'WARNING'
178
+ ? palette.warning
179
+ : palette.error;
180
+
181
+ return (
182
+ <View
183
+ key={`${operation?.operation || 'operation'}-${index}`}
184
+ style={[
185
+ styles.opChip,
186
+ {
187
+ borderColor: withOpacity(tone, 0.3),
188
+ backgroundColor: withOpacity(tone, 0.08),
189
+ },
190
+ ]}>
191
+ <Icon name={online ? 'check-circle' : 'x-circle'} size={12} color={tone} />
192
+ <Text style={[styles.opChipText, { color: tone }]}>
193
+ {operation.operation}
194
+ {operation.sales_channel ? ` · ${operation.sales_channel}` : ''}
195
+ {`: ${operation.state_label}`}
196
+ </Text>
197
+ </View>
198
+ );
199
+ })}
200
+ </View>
201
+ );
202
+ })()}
203
+
204
+ <View style={styles.availRow}>
205
+ <TouchableOpacity
206
+ activeOpacity={0.9}
207
+ style={[
208
+ styles.availButton,
209
+ { backgroundColor: palette.success },
210
+ (actionLoading === 'store_open' || storeStatus?.data?.online === true) && styles.availButtonDisabled,
211
+ ]}
212
+ onPress={onStoreOpen}
213
+ disabled={actionLoading !== null || storeStatus?.data?.online === true}>
214
+ {actionLoading === 'store_open' ? (
215
+ <ActivityIndicator color={palette.white} size="small" />
216
+ ) : (
217
+ <>
218
+ <Icon name="check-circle" size={15} color={palette.white} />
219
+ <Text style={styles.availButtonText}>Abrir loja</Text>
220
+ </>
221
+ )}
222
+ </TouchableOpacity>
223
+
224
+ <TouchableOpacity
225
+ activeOpacity={0.9}
226
+ style={[
227
+ styles.availButton,
228
+ { backgroundColor: palette.error },
229
+ (actionLoading === 'store_close' || storeStatus?.data?.online === false) && styles.availButtonDisabled,
230
+ ]}
231
+ onPress={onStoreClose}
232
+ disabled={actionLoading !== null || storeStatus?.data?.online === false}>
233
+ {actionLoading === 'store_close' ? (
234
+ <ActivityIndicator color={palette.white} size="small" />
235
+ ) : (
236
+ <>
237
+ <Icon name="x-circle" size={15} color={palette.white} />
238
+ <Text style={styles.availButtonText}>Fechar loja</Text>
239
+ </>
240
+ )}
241
+ </TouchableOpacity>
242
+
243
+ <TouchableOpacity
244
+ activeOpacity={0.9}
245
+ style={styles.availRefreshButton}
246
+ onPress={onRefreshStatus}
247
+ disabled={actionLoading !== null}>
248
+ <Icon name="refresh-cw" size={15} color={palette.textSecondary} />
249
+ </TouchableOpacity>
250
+ </View>
251
+
252
+ {storeStatus?.errno !== 0 && storeStatus?.errno != null && (
253
+ <View style={styles.errorBox}>
254
+ <Text style={styles.errorTitle}>Erro ao consultar disponibilidade</Text>
255
+ <Text style={styles.errorText}>{storeStatus.errmsg}</Text>
256
+ </View>
257
+ )}
258
+ </View>
259
+
260
+ <View style={[styles.sectionCard, shadowStyle]}>
261
+ <View style={styles.statusRow}>
262
+ <Text style={styles.sectionTitle}>Horarios de funcionamento</Text>
263
+ {!hoursEditing && (
264
+ <TouchableOpacity
265
+ onPress={onStartEditHours}
266
+ style={styles.hoursEditBtn}
267
+ disabled={hoursLoading}>
268
+ <Icon name="edit-2" size={13} color={palette.info} />
269
+ <Text style={styles.hoursEditBtnText}>Editar</Text>
270
+ </TouchableOpacity>
271
+ )}
272
+ </View>
273
+
274
+ {hoursLoading && <ActivityIndicator size="small" color={palette.info} />}
275
+
276
+ {!hoursLoading && !hoursEditing && (
277
+ Array.isArray(hours) && hours.length > 0 ? (
278
+ <View style={styles.hoursList}>
279
+ {DAY_ORDER.map(day => {
280
+ const entry = hours.find(item => item.dayOfWeek === day);
281
+ const isOpen = !!entry && (entry.shifts || []).length > 0;
282
+
283
+ return (
284
+ <View key={day} style={styles.hoursDayRow}>
285
+ <Text style={[styles.hoursDayLabel, !isOpen && { color: palette.textSecondary }]}>
286
+ {DAY_LABELS[day]}
287
+ </Text>
288
+ <View style={styles.hoursValues}>
289
+ {isOpen ? (
290
+ (entry.shifts || []).map((shift, index) => (
291
+ <Text key={`${day}-${index}`} style={styles.hoursValue}>
292
+ {shift.start} – {calcEndTime(shift.start, shift.duration)}
293
+ </Text>
294
+ ))
295
+ ) : (
296
+ <Text style={styles.hoursClosed}>Fechado</Text>
297
+ )}
298
+ </View>
299
+ </View>
300
+ );
301
+ })}
302
+ </View>
303
+ ) : (
304
+ <Text style={styles.helperText}>Nenhum horario configurado. Toque em Editar para definir.</Text>
305
+ )
306
+ )}
307
+
308
+ {hoursEditing && Array.isArray(hoursDraft) && (
309
+ hoursDraft.length > 0 ? (
310
+ <View style={styles.hoursEditList}>
311
+ {DAY_ORDER.map(day => {
312
+ const entry =
313
+ hoursDraft.find(item => item.dayOfWeek === day) || {
314
+ dayOfWeek: day,
315
+ open: false,
316
+ shifts: [{ start: '09:00', duration: 840 }],
317
+ };
318
+
319
+ return (
320
+ <View key={day} style={[styles.hoursDayRow, { alignItems: 'center' }]}>
321
+ <TouchableOpacity
322
+ onPress={() => onUpdateHoursDraft(day, null, 'open', !entry.open)}
323
+ disabled={hoursSaving}
324
+ style={[styles.dayToggle, entry.open && styles.dayToggleOn]}>
325
+ <View style={[styles.dayToggleThumb, entry.open && styles.dayToggleThumbOn]} />
326
+ </TouchableOpacity>
327
+
328
+ <Text style={[styles.hoursDayLabel, !entry.open && { color: palette.textSecondary }]}>
329
+ {DAY_LABELS[day]}
330
+ </Text>
331
+
332
+ {entry.open ? (
333
+ <View style={styles.hoursEditContent}>
334
+ {(entry.shifts || []).map((shift, index) => (
335
+ <View key={`${day}-shift-${index}`} style={styles.hoursInputRow}>
336
+ <TextInput
337
+ value={shift.start}
338
+ onChangeText={value => onUpdateHoursDraft(day, index, 'start', value)}
339
+ style={styles.hoursInput}
340
+ placeholder="HH:MM"
341
+ placeholderTextColor={palette.textSecondary}
342
+ keyboardType="numbers-and-punctuation"
343
+ editable={!hoursSaving}
344
+ />
345
+ <Text style={styles.hoursSep}>–</Text>
346
+ <TextInput
347
+ value={calcEndTime(shift.start, shift.duration)}
348
+ onChangeText={value => onUpdateHoursDraft(day, index, 'end', value)}
349
+ style={styles.hoursInput}
350
+ placeholder="HH:MM"
351
+ placeholderTextColor={palette.textSecondary}
352
+ keyboardType="numbers-and-punctuation"
353
+ editable={!hoursSaving}
354
+ />
355
+ {(entry.shifts || []).length > 1 && (
356
+ <TouchableOpacity
357
+ onPress={() => onRemoveHoursShift(day, index)}
358
+ disabled={hoursSaving}
359
+ style={styles.hoursShiftRemoveBtn}>
360
+ <Icon name="trash-2" size={14} color={palette.error} />
361
+ </TouchableOpacity>
362
+ )}
363
+ </View>
364
+ ))}
365
+ <TouchableOpacity
366
+ onPress={() => onAddHoursShift(day)}
367
+ disabled={hoursSaving}
368
+ style={styles.hoursShiftAddBtn}>
369
+ <Icon name="plus" size={14} color={palette.info} />
370
+ <Text style={styles.hoursShiftAddText}>Adicionar turno</Text>
371
+ </TouchableOpacity>
372
+ </View>
373
+ ) : (
374
+ <Text style={styles.hoursClosed}>Fechado</Text>
375
+ )}
376
+ </View>
377
+ );
378
+ })}
379
+
380
+ <View style={styles.availRow}>
381
+ <TouchableOpacity
382
+ onPress={onSaveHours}
383
+ disabled={hoursSaving}
384
+ style={[styles.actionButton, { backgroundColor: palette.success }]}>
385
+ {hoursSaving ? (
386
+ <ActivityIndicator color={palette.white} size="small" />
387
+ ) : (
388
+ <>
389
+ <Icon name="check" size={15} color={palette.white} />
390
+ <Text style={styles.actionButtonText}>Salvar horarios</Text>
391
+ </>
392
+ )}
393
+ </TouchableOpacity>
394
+
395
+ <TouchableOpacity
396
+ onPress={onCancelEditHours}
397
+ disabled={hoursSaving}
398
+ style={styles.hoursCancelBtn}>
399
+ <Text style={styles.hoursCancelText}>Cancelar</Text>
400
+ </TouchableOpacity>
401
+ </View>
402
+ </View>
403
+ ) : (
404
+ <View style={styles.hoursEditList}>
405
+ <View style={styles.helperRow}>
406
+ <Text style={styles.helperText}>
407
+ Nenhum horario retornado pelo iFood. Sincronize o catalogo e tente novamente.
408
+ </Text>
409
+ </View>
410
+
411
+ <TouchableOpacity onPress={onCancelEditHours} style={styles.hoursCancelBtn}>
412
+ <Text style={styles.hoursCancelText}>Fechar</Text>
413
+ </TouchableOpacity>
414
+ </View>
415
+ )
416
+ )}
417
+ </View>
418
+ </>
419
+ );
420
+ }
@@ -0,0 +1,118 @@
1
+ import React from 'react';
2
+ import { Text, View } from 'react-native';
3
+
4
+ import { withOpacity } from '@controleonline/../../src/styles/branding';
5
+
6
+ import styles from '../styles';
7
+
8
+ // Aba de resumo da integração iFood.
9
+ export default function IFoodOverviewTab({
10
+ shadowStyle,
11
+ integration,
12
+ connected,
13
+ remoteConnected,
14
+ authAvailable,
15
+ statusTone,
16
+ statusText,
17
+ eligibleCount,
18
+ selectedStoreDetail,
19
+ }) {
20
+ return (
21
+ <View style={[styles.sectionCard, shadowStyle]}>
22
+ <View style={styles.statusRow}>
23
+ <Text style={styles.sectionTitle}>Status da integracao</Text>
24
+ <View style={[styles.statusBadge, { backgroundColor: withOpacity(statusTone, 0.12) }]}>
25
+ <Text style={[styles.statusBadgeText, { color: statusTone }]}>{statusText}</Text>
26
+ </View>
27
+ </View>
28
+
29
+ <View style={styles.metaGrid}>
30
+ <View style={styles.metaBox}>
31
+ <Text style={styles.metaLabel}>Loja vinculada</Text>
32
+ <Text style={styles.metaValue}>{integration?.merchant_name || integration?.merchant_id || 'Nao vinculada'}</Text>
33
+ </View>
34
+ <View style={styles.metaBox}>
35
+ <Text style={styles.metaLabel}>Status remoto</Text>
36
+ <Text style={styles.metaValue}>{integration?.merchant_status_label || 'Indefinido'}</Text>
37
+ </View>
38
+ </View>
39
+
40
+ <View style={styles.metaGrid}>
41
+ <View style={styles.metaBox}>
42
+ <Text style={styles.metaLabel}>Token OAuth</Text>
43
+ <Text style={styles.metaValue}>{authAvailable ? 'Disponível' : 'Indisponível'}</Text>
44
+ </View>
45
+ <View style={styles.metaBox}>
46
+ <Text style={styles.metaLabel}>Produtos aptos</Text>
47
+ <Text style={styles.metaValue}>{eligibleCount}</Text>
48
+ </View>
49
+ </View>
50
+
51
+ <View style={styles.helperRow}>
52
+ <Text style={styles.helperText}>
53
+ {remoteConnected
54
+ ? 'A vinculação local está confirmada na conta iFood.'
55
+ : connected
56
+ ? 'Loja vinculada localmente. Execute sincronização para validar no iFood.'
57
+ : 'Selecione uma loja ou informe o código iFood para conectar.'}
58
+ </Text>
59
+ </View>
60
+
61
+ {!!integration?.last_error_message && (
62
+ <View style={styles.errorBox}>
63
+ <Text style={styles.errorTitle}>Último erro</Text>
64
+ <Text style={styles.errorText}>{integration.last_error_message}</Text>
65
+ </View>
66
+ )}
67
+
68
+ {!!selectedStoreDetail && (
69
+ <View style={styles.selectedStoreBox}>
70
+ <Text style={styles.selectedStoreTitle}>Detalhes da loja</Text>
71
+
72
+ <View style={styles.metaGrid}>
73
+ <View style={styles.metaBox}>
74
+ <Text style={styles.metaLabel}>Nome fantasia</Text>
75
+ <Text style={styles.metaValue}>{selectedStoreDetail?.name || '--'}</Text>
76
+ </View>
77
+ <View style={styles.metaBox}>
78
+ <Text style={styles.metaLabel}>Status</Text>
79
+ <Text style={styles.metaValue}>{selectedStoreDetail?.status_label || '--'}</Text>
80
+ </View>
81
+ </View>
82
+
83
+ <View style={styles.metaGrid}>
84
+ <View style={styles.metaBox}>
85
+ <Text style={styles.metaLabel}>Razão social</Text>
86
+ <Text style={styles.metaValue}>{selectedStoreDetail?.corporate_name || '--'}</Text>
87
+ </View>
88
+ <View style={styles.metaBox}>
89
+ <Text style={styles.metaLabel}>Tipo</Text>
90
+ <Text style={styles.metaValue}>{selectedStoreDetail?.type || '--'}</Text>
91
+ </View>
92
+ </View>
93
+
94
+ <View style={styles.metaBox}>
95
+ <Text style={styles.metaLabel}>Endereço</Text>
96
+ <Text style={styles.metaValue}>
97
+ {selectedStoreDetail?.address?.formatted || 'Endereço não informado'}
98
+ </Text>
99
+ </View>
100
+
101
+ {Array.isArray(selectedStoreDetail?.operations) && selectedStoreDetail.operations.length > 0 && (
102
+ <View style={styles.storeOperationsBox}>
103
+ <Text style={styles.metaLabel}>Operações configuradas</Text>
104
+ {selectedStoreDetail.operations.map((operation, index) => (
105
+ <Text key={`${operation?.name || 'operation'}-${index}`} style={styles.operationText}>
106
+ {operation?.name || 'Operação'}
107
+ {Array.isArray(operation?.sales_channels) && operation.sales_channels.length > 0
108
+ ? ` - ${operation.sales_channels.join(', ')}`
109
+ : ''}
110
+ </Text>
111
+ ))}
112
+ </View>
113
+ )}
114
+ </View>
115
+ )}
116
+ </View>
117
+ );
118
+ }
@@ -0,0 +1,103 @@
1
+ import React from 'react';
2
+ import { ActivityIndicator, ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { colors } from '@controleonline/../../src/styles/colors';
5
+
6
+ import AnimatedModal from '@controleonline/ui-common/src/react/components/AnimatedModal';
7
+
8
+ import styles from '../styles';
9
+ import { countCollection } from '@controleonline/ui-common/src/react/utils/integrationPage';
10
+
11
+ // Modal de pré-visualização do menu antes do envio ao iFood.
12
+ export default function IFoodPreviewModal({
13
+ visible,
14
+ previewData,
15
+ selectedEligible,
16
+ accentColor,
17
+ uploading,
18
+ palette = colors,
19
+ onClose,
20
+ onUpload,
21
+ }) {
22
+ return (
23
+ <AnimatedModal visible={visible} onRequestClose={onClose}>
24
+ <View style={styles.modalShell}>
25
+ <View style={styles.modalCard}>
26
+ <View style={styles.modalHeader}>
27
+ <View>
28
+ <Text style={styles.modalTitle}>Pre-visualizacao do menu</Text>
29
+ <Text style={styles.modalSubtitle}>
30
+ {previewData?.eligible_product_count || selectedEligible.length} produtos prontos para upload
31
+ </Text>
32
+ </View>
33
+
34
+ <TouchableOpacity onPress={onClose} style={styles.modalCloseButton}>
35
+ <Icon name="x" size={18} color={palette.textSecondary} />
36
+ </TouchableOpacity>
37
+ </View>
38
+
39
+ <ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.modalContent}>
40
+ <View style={styles.modalSummaryGrid}>
41
+ <View style={styles.modalSummaryCard}>
42
+ <Text style={styles.modalSummaryValue}>{countCollection(previewData?.payload?.menus)}</Text>
43
+ <Text style={styles.modalSummaryLabel}>Menus</Text>
44
+ </View>
45
+ <View style={styles.modalSummaryCard}>
46
+ <Text style={styles.modalSummaryValue}>{countCollection(previewData?.payload?.categories)}</Text>
47
+ <Text style={styles.modalSummaryLabel}>Categorias</Text>
48
+ </View>
49
+ <View style={styles.modalSummaryCard}>
50
+ <Text style={styles.modalSummaryValue}>{countCollection(previewData?.payload?.items)}</Text>
51
+ <Text style={styles.modalSummaryLabel}>Itens</Text>
52
+ </View>
53
+ </View>
54
+
55
+ <View style={styles.previewSection}>
56
+ <Text style={styles.previewSectionTitle}>Categorias</Text>
57
+ {(previewData?.payload?.categories || []).map(category => (
58
+ <View key={category.app_category_id} style={styles.previewLine}>
59
+ <Text style={styles.previewLineTitle}>{category.category_name}</Text>
60
+ <Text style={styles.previewLineMeta}>
61
+ {countCollection(category.app_item_ids)} item(ns)
62
+ </Text>
63
+ </View>
64
+ ))}
65
+ </View>
66
+
67
+ <View style={styles.previewSection}>
68
+ <Text style={styles.previewSectionTitle}>Itens selecionados</Text>
69
+ {selectedEligible.map(product => (
70
+ <View key={product.id} style={styles.previewLine}>
71
+ <Text style={styles.previewLineTitle}>{product.name}</Text>
72
+ <Text style={styles.previewLineMeta}>
73
+ {product.category?.name || 'Sem categoria'} • R$ {Number(product.price || 0).toFixed(2)}
74
+ </Text>
75
+ </View>
76
+ ))}
77
+ </View>
78
+ </ScrollView>
79
+
80
+ <View style={styles.modalActions}>
81
+ <TouchableOpacity style={styles.secondaryButton} onPress={onClose}>
82
+ <Text style={styles.secondaryButtonText}>Fechar</Text>
83
+ </TouchableOpacity>
84
+
85
+ <TouchableOpacity
86
+ style={[styles.primaryButton, styles.modalPrimaryButton, { backgroundColor: accentColor }]}
87
+ onPress={onUpload}
88
+ disabled={uploading}>
89
+ {uploading ? (
90
+ <ActivityIndicator size="small" color={palette.white} />
91
+ ) : (
92
+ <>
93
+ <Icon name="upload-cloud" size={16} color={palette.white} />
94
+ <Text style={styles.primaryButtonText}>Publicar menu</Text>
95
+ </>
96
+ )}
97
+ </TouchableOpacity>
98
+ </View>
99
+ </View>
100
+ </View>
101
+ </AnimatedModal>
102
+ );
103
+ }
@@ -0,0 +1 @@
1
+ export {default} from '../../MarketplaceIntegrationPage/components/MarketplaceProductCard';