@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.
- package/.github/actions/workers/manager/action.yml +147 -0
- package/.github/actions/workers/qa/action.yml +44 -0
- package/.github/actions/workers/security/action.yml +44 -0
- package/.github/actions/workers/technical-documenter/action.yml +51 -0
- package/.github/workflows/manager-worker.yml +168 -0
- package/.github/workflows/publish-npm.yml +45 -0
- package/.gitmodules +3 -0
- package/AGENTS.md +6 -0
- package/README.md +14 -0
- package/package.json +3 -2
- package/src/api/fetch.js +1 -1
- package/src/api/index.js +92 -22
- package/src/react/components/AddImportModal.js +94 -110
- package/src/react/components/BottomNavigationBar.config.js +211 -231
- package/src/react/components/CategoryForm.js +52 -35
- package/src/react/components/DefaultProvider.native.js +131 -95
- package/src/react/components/DefaultProvider.web.js +148 -74
- package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
- package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
- package/src/react/components/RuntimeInfoFooter.js +100 -36
- package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
- package/src/react/components/UnifiedPaymentBar.js +7 -4
- package/src/react/components/UserAvatar.js +160 -0
- package/src/react/components/integrations/IntegrationHero/index.js +48 -0
- package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
- package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
- package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
- package/src/react/config/deviceConfigBootstrap.js +5 -3
- package/src/react/pages/Connections.js +229 -0
- package/src/react/pages/Connections.styles.js +222 -0
- package/src/react/pages/DeviceDetailPage.js +2814 -0
- package/src/react/pages/DeviceDetailPage.styles.js +308 -0
- package/src/react/pages/Devices/currentDevice.js +194 -0
- package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
- package/src/react/pages/Devices/deviceTypes/all.js +23 -0
- package/src/react/pages/Devices/deviceTypes/device.js +25 -0
- package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
- package/src/react/pages/Devices/deviceTypes/display.js +26 -0
- package/src/react/pages/Devices/deviceTypes/index.js +145 -0
- package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
- package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
- package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
- package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
- package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
- package/src/react/pages/Devices.js +183 -0
- package/src/react/pages/Devices.styles.js +353 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
- package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
- package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
- package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
- package/src/react/pages/Food99IntegrationPage.js +1 -0
- package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
- package/src/react/pages/GenericLogPage.js +42 -167
- package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
- package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
- package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
- package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
- package/src/react/pages/IFoodIntegrationPage.js +1 -0
- package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
- package/src/react/pages/Imports.js +214 -166
- package/src/react/pages/IntegrationConfigPage.js +744 -0
- package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
- package/src/react/pages/Integrations.js +299 -0
- package/src/react/pages/Integrations.styles.js +130 -0
- package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
- package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
- package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
- package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
- package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
- package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
- package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
- package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
- package/src/react/pages/PrinterDeviceFormPage.js +521 -0
- package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
- package/src/react/pages/WhatsAppConnectionPage.js +459 -0
- package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
- package/src/react/pages/integrationsCatalog.js +399 -0
- package/src/react/services/Cielo/Cielo.js +193 -193
- package/src/react/utils/categoryContexts.js +46 -0
- package/src/react/utils/entityDisplay.js +222 -217
- package/src/react/utils/fileUrl.js +39 -20
- package/src/react/utils/integrationPage.js +16 -0
- package/src/react/utils/runtimeFooter.js +40 -22
- package/src/react/utils/runtimeLanguage.js +50 -9
- package/src/react/utils/runtimeMenu.js +191 -131
- package/src/react/utils/runtimeZoom.js +43 -0
- package/src/react/utils/shopConfig.js +27 -15
- package/src/react/utils/shopFranchises.js +126 -1
- package/src/react/utils/storeColumns.js +23 -4
- package/src/react/utils/userAvatar.js +104 -0
- package/src/store/categories/index.js +48 -20
- package/src/store/device_config/index.js +62 -21
- package/src/store/entity_log/index.js +22 -1
- package/src/store/imports/customActions.js +51 -0
- package/src/store/imports/index.js +89 -88
- package/src/store/status/index.js +11 -11
- package/src/store/themes/index.js +22 -0
- package/src/store/timezones/index.js +3 -1
- package/src/tests/browser/manager/connections.spec.js +143 -0
- package/src/tests/browser/manager/devices-current.spec.js +343 -0
- package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
- package/src/tests/react/api/deviceHeaders.test.js +114 -0
- package/src/tests/react/components/AddImportModal.test.js +166 -0
- package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
- package/src/tests/react/components/UserAvatar.test.js +52 -0
- package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
- package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
- package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
- package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
- package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
- package/src/tests/react/services/Cielo.test.js +190 -190
- package/src/tests/react/store/categoriesOrdering.test.js +26 -0
- package/src/tests/react/utils/appDomain.test.js +62 -0
- package/src/tests/react/utils/entityDisplay.test.js +2 -2
- package/src/tests/react/utils/fileUrl.test.js +3 -3
- package/src/tests/react/utils/formatter.test.mjs +33 -0
- package/src/tests/react/utils/runtimeFooter.test.js +29 -4
- package/src/tests/react/utils/runtimeLanguage.test.js +79 -9
- package/src/tests/react/utils/runtimeMenu.test.js +198 -134
- package/src/tests/react/utils/runtimeZoom.test.js +38 -0
- package/src/tests/react/utils/shopConfig.test.js +10 -9
- package/src/tests/react/utils/shopFranchises.test.js +43 -0
- package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
- package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
- package/src/tests/react/utils/translate.test.mjs +141 -349
- package/src/tests/react/utils/userAvatar.test.js +63 -0
- package/src/utils/appDomain.js +15 -4
- package/src/utils/formatter.js +104 -16
- package/src/utils/integrationConfigs.js +80 -80
- package/src/utils/translate.js +222 -438
- package/src/utils/translateDiscovery.js +96 -0
- package/src/utils/translateNormalize.js +33 -0
- package/src/utils/translatePending.js +92 -0
- package/src/utils/translateStorage.js +142 -0
- package/.github/agents/developer.agent.md +0 -30
- package/.github/agents/devops.agent.md +0 -30
- package/.github/agents/qa.agent.md +0 -30
- package/.github/agents/security.agent.md +0 -30
- 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';
|