@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,1530 @@
|
|
|
1
|
+
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Linking,
|
|
5
|
+
RefreshControl,
|
|
6
|
+
ScrollView,
|
|
7
|
+
Text,
|
|
8
|
+
View,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import {SafeAreaView} from 'react-native-safe-area-context';
|
|
11
|
+
import {useFocusEffect} from '@react-navigation/native';
|
|
12
|
+
import Icon from 'react-native-vector-icons/Feather';
|
|
13
|
+
|
|
14
|
+
import {api} from '@controleonline/ui-common/src/api';
|
|
15
|
+
import useToastMessage from '@controleonline/ui-crm/src/react/hooks/useToastMessage';
|
|
16
|
+
import {useStore} from '@store';
|
|
17
|
+
import {colors} from '@controleonline/../../src/styles/colors';
|
|
18
|
+
import {resolveThemePalette} from '@controleonline/../../src/styles/branding';
|
|
19
|
+
import {getOrderChannelLogo} from '@assets/ppc/channels';
|
|
20
|
+
|
|
21
|
+
import IntegrationHero from '../../components/integrations/IntegrationHero';
|
|
22
|
+
import IntegrationTabs from '../../components/integrations/IntegrationTabs';
|
|
23
|
+
import {integrationCardShadowStyle} from '@controleonline/ui-common/src/react/utils/integrationPage';
|
|
24
|
+
import Food99CatalogTab from './components/Food99CatalogTab';
|
|
25
|
+
import Food99OverviewTab from './components/Food99OverviewTab';
|
|
26
|
+
import Food99PreviewModal from './components/Food99PreviewModal';
|
|
27
|
+
import Food99SettingsTab from './components/Food99SettingsTab';
|
|
28
|
+
import Food99QuickWalletModal from './components/Food99QuickWalletModal';
|
|
29
|
+
import Food99StoreTab from './components/Food99StoreTab';
|
|
30
|
+
import RemoteMarketplaceMenuTab from '../MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab';
|
|
31
|
+
import styles from './styles';
|
|
32
|
+
import {
|
|
33
|
+
createEmptyStoreSettingsDraft,
|
|
34
|
+
formatDeliveryMethodLabel,
|
|
35
|
+
formatFood99ApiError,
|
|
36
|
+
getPublishStateLabel,
|
|
37
|
+
isErrnoSuccess,
|
|
38
|
+
isValidTimeInput,
|
|
39
|
+
MINIMUM_REQUIRED_ITEMS,
|
|
40
|
+
normalizeDeliveryMethodCode,
|
|
41
|
+
normalizeTaskId,
|
|
42
|
+
publishStateToneMap,
|
|
43
|
+
resolveFood99HistoryFromTime,
|
|
44
|
+
sanitizeConfirmMethodInput,
|
|
45
|
+
sanitizeTimeInput,
|
|
46
|
+
statusLabelMap,
|
|
47
|
+
subStatusLabelMap,
|
|
48
|
+
wait,
|
|
49
|
+
} from './utils';
|
|
50
|
+
|
|
51
|
+
const openAuthorizationUrl = async authUrl => {
|
|
52
|
+
if (
|
|
53
|
+
typeof window !== 'undefined' &&
|
|
54
|
+
typeof window.location?.assign === 'function'
|
|
55
|
+
) {
|
|
56
|
+
window.location.assign(authUrl);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await Linking.openURL(authUrl);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Página principal da integração 99Food com separação por contexto.
|
|
64
|
+
export default function Food99IntegrationPage() {
|
|
65
|
+
const peopleStore = useStore('people');
|
|
66
|
+
const walletStore = useStore('wallet');
|
|
67
|
+
const themeStore = useStore('theme');
|
|
68
|
+
const {currentCompany} = peopleStore.getters;
|
|
69
|
+
const {items: wallets = [], isLoading: isLoadingWallets} =
|
|
70
|
+
walletStore.getters;
|
|
71
|
+
const {colors: themeColors} = themeStore.getters;
|
|
72
|
+
const {showError, showInfo, showSuccess} = useToastMessage();
|
|
73
|
+
|
|
74
|
+
const brandColors = useMemo(
|
|
75
|
+
() =>
|
|
76
|
+
resolveThemePalette(
|
|
77
|
+
{
|
|
78
|
+
...themeColors,
|
|
79
|
+
...(currentCompany?.theme?.colors || {}),
|
|
80
|
+
},
|
|
81
|
+
colors,
|
|
82
|
+
),
|
|
83
|
+
[themeColors, currentCompany?.id],
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const heroPalette = useMemo(
|
|
87
|
+
() => ({
|
|
88
|
+
...brandColors,
|
|
89
|
+
actionBackground:
|
|
90
|
+
brandColors.buttonBackground ||
|
|
91
|
+
brandColors.actionBackground ||
|
|
92
|
+
brandColors.primary,
|
|
93
|
+
actionText:
|
|
94
|
+
brandColors.buttonText ||
|
|
95
|
+
brandColors.actionText ||
|
|
96
|
+
brandColors.white,
|
|
97
|
+
cardIconBackground:
|
|
98
|
+
brandColors.cardIconBackground ||
|
|
99
|
+
brandColors.iconBackground ||
|
|
100
|
+
brandColors.white,
|
|
101
|
+
cardIconColor:
|
|
102
|
+
brandColors.cardIconColor ||
|
|
103
|
+
brandColors.iconColor ||
|
|
104
|
+
brandColors.buttonText ||
|
|
105
|
+
brandColors.textPrimary,
|
|
106
|
+
}),
|
|
107
|
+
[brandColors],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const providerId = currentCompany?.id;
|
|
111
|
+
const logo = getOrderChannelLogo({app: '99Food'});
|
|
112
|
+
|
|
113
|
+
const [activeTab, setActiveTab] = useState('overview');
|
|
114
|
+
const [loading, setLoading] = useState(true);
|
|
115
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
116
|
+
const [search, setSearch] = useState('');
|
|
117
|
+
const [filterKey, setFilterKey] = useState('all');
|
|
118
|
+
const [integrationItem, setIntegrationItem] = useState(null);
|
|
119
|
+
const [productsResponse, setProductsResponse] = useState(null);
|
|
120
|
+
const [selectedProductIds, setSelectedProductIds] = useState([]);
|
|
121
|
+
const [previewData, setPreviewData] = useState(null);
|
|
122
|
+
const [previewVisible, setPreviewVisible] = useState(false);
|
|
123
|
+
const [previewLoading, setPreviewLoading] = useState(false);
|
|
124
|
+
const [uploading, setUploading] = useState(false);
|
|
125
|
+
const [actionLoading, setActionLoading] = useState('');
|
|
126
|
+
const [lastUploadResult, setLastUploadResult] = useState(null);
|
|
127
|
+
const [storeSettingsResponse, setStoreSettingsResponse] = useState(null);
|
|
128
|
+
const [storeSettingsDraft, setStoreSettingsDraft] = useState(
|
|
129
|
+
createEmptyStoreSettingsDraft,
|
|
130
|
+
);
|
|
131
|
+
const [manualShopId, setManualShopId] = useState('');
|
|
132
|
+
const [quickWalletModalVisible, setQuickWalletModalVisible] = useState(false);
|
|
133
|
+
const [quickWalletName, setQuickWalletName] = useState('');
|
|
134
|
+
const [remoteMenuSnapshot, setRemoteMenuSnapshot] = useState(null);
|
|
135
|
+
const [remoteMenuError, setRemoteMenuError] = useState('');
|
|
136
|
+
const hasHydratedSelection = useRef(false);
|
|
137
|
+
const ignoreNextProductCardPressRef = useRef(false);
|
|
138
|
+
const hasLoadedOnceRef = useRef(false);
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
hasLoadedOnceRef.current = false;
|
|
142
|
+
}, [providerId]);
|
|
143
|
+
|
|
144
|
+
const products = useMemo(
|
|
145
|
+
() =>
|
|
146
|
+
Array.isArray(productsResponse?.products)
|
|
147
|
+
? productsResponse.products
|
|
148
|
+
: [],
|
|
149
|
+
[productsResponse],
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const selectedProductSet = useMemo(
|
|
153
|
+
() => new Set(selectedProductIds.map(id => String(id))),
|
|
154
|
+
[selectedProductIds],
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const productMap = useMemo(() => {
|
|
158
|
+
const map = new Map();
|
|
159
|
+
products.forEach(product => {
|
|
160
|
+
map.set(String(product.id), product);
|
|
161
|
+
});
|
|
162
|
+
return map;
|
|
163
|
+
}, [products]);
|
|
164
|
+
|
|
165
|
+
const selectedProducts = useMemo(
|
|
166
|
+
() =>
|
|
167
|
+
selectedProductIds.map(id => productMap.get(String(id))).filter(Boolean),
|
|
168
|
+
[productMap, selectedProductIds],
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const selectedEligibleProducts = useMemo(
|
|
172
|
+
() => selectedProducts.filter(product => product.eligible),
|
|
173
|
+
[selectedProducts],
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const connected = Boolean(integrationItem?.connected);
|
|
177
|
+
const remoteConnected = Boolean(integrationItem?.remote_connected);
|
|
178
|
+
const needsReconnect = connected && !remoteConnected;
|
|
179
|
+
const storeBizStatus = Number(integrationItem?.biz_status ?? 0);
|
|
180
|
+
const storeSubStatus = Number(integrationItem?.sub_biz_status ?? 0);
|
|
181
|
+
const isOnline = Boolean(integrationItem?.online);
|
|
182
|
+
const menuCount = Number(integrationItem?.menu_count || 0);
|
|
183
|
+
const menuItemCount = Number(integrationItem?.menu_item_count || 0);
|
|
184
|
+
const deliveryAreaCount = Number(integrationItem?.delivery_area_count || 0);
|
|
185
|
+
const publishedProductCount = Number(
|
|
186
|
+
integrationItem?.published_product_count || 0,
|
|
187
|
+
);
|
|
188
|
+
const remoteOnlyItemCount = Number(
|
|
189
|
+
integrationItem?.remote_only_item_count || 0,
|
|
190
|
+
);
|
|
191
|
+
const lastSyncAt = integrationItem?.last_sync_at || null;
|
|
192
|
+
const lastErrorMessage = integrationItem?.last_error_message || null;
|
|
193
|
+
const lastMenuTaskId =
|
|
194
|
+
normalizeTaskId(integrationItem?.last_menu_task_id) ||
|
|
195
|
+
normalizeTaskId(lastUploadResult?.integration?.last_menu_task_id) ||
|
|
196
|
+
normalizeTaskId(lastUploadResult?.result?.data?.taskID) ||
|
|
197
|
+
normalizeTaskId(lastUploadResult?.result?.data?.taskId) ||
|
|
198
|
+
null;
|
|
199
|
+
const lastMenuTaskStatus = integrationItem?.last_menu_task_status || null;
|
|
200
|
+
const lastMenuTaskMessage = integrationItem?.last_menu_task_message || null;
|
|
201
|
+
const lastMenuTaskCheckedAt =
|
|
202
|
+
integrationItem?.last_menu_task_checked_at || null;
|
|
203
|
+
const lastMenuPublishState = integrationItem?.last_menu_publish_state || null;
|
|
204
|
+
const lastReconcileAt = integrationItem?.last_reconcile_at || null;
|
|
205
|
+
const lastWebhookReceivedAt =
|
|
206
|
+
integrationItem?.last_webhook_received_at || null;
|
|
207
|
+
const publicationTone =
|
|
208
|
+
publishStateToneMap[lastMenuPublishState] || brandColors.textSecondary;
|
|
209
|
+
const storeSettings = storeSettingsResponse?.settings || {};
|
|
210
|
+
const settingsSource = String(
|
|
211
|
+
storeSettingsResponse?.settings_source || 'unavailable',
|
|
212
|
+
);
|
|
213
|
+
const settingsSourceLabelMap = {
|
|
214
|
+
remote: 'Remoto',
|
|
215
|
+
mixed: 'Remoto + fallback local',
|
|
216
|
+
fallback: 'Fallback local',
|
|
217
|
+
unavailable: 'Indisponivel',
|
|
218
|
+
};
|
|
219
|
+
const settingsSourceLabel =
|
|
220
|
+
settingsSourceLabelMap[settingsSource] || settingsSource;
|
|
221
|
+
|
|
222
|
+
const resolveSettingDisplay = value =>
|
|
223
|
+
value === null || value === undefined || String(value).trim() === ''
|
|
224
|
+
? '-'
|
|
225
|
+
: String(value);
|
|
226
|
+
|
|
227
|
+
const currentDeliveryRadius = resolveSettingDisplay(
|
|
228
|
+
storeSettings?.delivery_radius,
|
|
229
|
+
);
|
|
230
|
+
const currentOpenTime = resolveSettingDisplay(storeSettings?.open_time);
|
|
231
|
+
const currentCloseTime = resolveSettingDisplay(storeSettings?.close_time);
|
|
232
|
+
const currentDeliveryMethod = formatDeliveryMethodLabel(
|
|
233
|
+
storeSettings?.delivery_method,
|
|
234
|
+
);
|
|
235
|
+
const currentConfirmMethod = resolveSettingDisplay(
|
|
236
|
+
storeSettings?.confirm_method,
|
|
237
|
+
);
|
|
238
|
+
const currentSettlementWalletId = String(
|
|
239
|
+
storeSettings?.settlement_wallet_id || '',
|
|
240
|
+
).trim();
|
|
241
|
+
const currentSettlementWalletLabel = useMemo(() => {
|
|
242
|
+
if (!currentSettlementWalletId) {
|
|
243
|
+
return '-';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const selectedWallet = wallets.find(
|
|
247
|
+
wallet => String(wallet.id) === currentSettlementWalletId,
|
|
248
|
+
);
|
|
249
|
+
if (selectedWallet) {
|
|
250
|
+
const walletLabel =
|
|
251
|
+
String(selectedWallet.wallet || '').trim() ||
|
|
252
|
+
`Carteira #${selectedWallet.id}`;
|
|
253
|
+
|
|
254
|
+
return `${walletLabel} (#${selectedWallet.id})`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return `#${currentSettlementWalletId}`;
|
|
258
|
+
}, [currentSettlementWalletId, wallets]);
|
|
259
|
+
|
|
260
|
+
const filteredProducts = useMemo(() => {
|
|
261
|
+
const normalizedSearch = String(search || '')
|
|
262
|
+
.trim()
|
|
263
|
+
.toLowerCase();
|
|
264
|
+
|
|
265
|
+
return products.filter(product => {
|
|
266
|
+
if (filterKey === 'eligible' && !product.eligible) return false;
|
|
267
|
+
if (filterKey === 'blocked' && product.eligible) return false;
|
|
268
|
+
if (
|
|
269
|
+
filterKey === 'selected' &&
|
|
270
|
+
!selectedProductSet.has(String(product.id))
|
|
271
|
+
)
|
|
272
|
+
return false;
|
|
273
|
+
|
|
274
|
+
if (!normalizedSearch) return true;
|
|
275
|
+
|
|
276
|
+
const haystack = [
|
|
277
|
+
product.name,
|
|
278
|
+
product.description,
|
|
279
|
+
product.category?.name,
|
|
280
|
+
product.food99_code,
|
|
281
|
+
product.suggested_app_item_id,
|
|
282
|
+
]
|
|
283
|
+
.filter(Boolean)
|
|
284
|
+
.join(' ')
|
|
285
|
+
.toLowerCase();
|
|
286
|
+
|
|
287
|
+
return haystack.includes(normalizedSearch);
|
|
288
|
+
});
|
|
289
|
+
}, [filterKey, products, search, selectedProductSet]);
|
|
290
|
+
|
|
291
|
+
const summaryCards = useMemo(
|
|
292
|
+
() => [
|
|
293
|
+
{
|
|
294
|
+
key: 'connection',
|
|
295
|
+
label: 'Loja 99',
|
|
296
|
+
value: needsReconnect
|
|
297
|
+
? 'Reconexão necessária'
|
|
298
|
+
: connected
|
|
299
|
+
? 'Conectada'
|
|
300
|
+
: 'Pendente',
|
|
301
|
+
icon: 'link',
|
|
302
|
+
color: needsReconnect ? '#e67e22' : connected ? '#10B981' : '#e67e22',
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
key: 'status',
|
|
306
|
+
label: 'Status',
|
|
307
|
+
value: remoteConnected
|
|
308
|
+
? integrationItem?.biz_status_label ||
|
|
309
|
+
statusLabelMap[storeBizStatus] ||
|
|
310
|
+
'Indefinido'
|
|
311
|
+
: needsReconnect
|
|
312
|
+
? 'Reconexão necessária'
|
|
313
|
+
: connected
|
|
314
|
+
? 'Aguardando sync'
|
|
315
|
+
: 'Pendente',
|
|
316
|
+
icon: isOnline ? 'wifi' : 'wifi-off',
|
|
317
|
+
color: isOnline ? brandColors.primary : '#64748B',
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
key: 'eligible',
|
|
321
|
+
label: 'Produtos locais aptos',
|
|
322
|
+
value: String(productsResponse?.eligible_product_count || 0),
|
|
323
|
+
icon: 'package',
|
|
324
|
+
color: '#3B82F6',
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
key: 'selected',
|
|
328
|
+
label: 'Selecionados',
|
|
329
|
+
value: String(selectedEligibleProducts.length),
|
|
330
|
+
icon: 'check-circle',
|
|
331
|
+
color:
|
|
332
|
+
selectedEligibleProducts.length >= MINIMUM_REQUIRED_ITEMS
|
|
333
|
+
? '#10B981'
|
|
334
|
+
: '#e67e22',
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
[
|
|
338
|
+
brandColors.primary,
|
|
339
|
+
connected,
|
|
340
|
+
needsReconnect,
|
|
341
|
+
remoteConnected,
|
|
342
|
+
integrationItem?.biz_status_label,
|
|
343
|
+
isOnline,
|
|
344
|
+
productsResponse?.eligible_product_count,
|
|
345
|
+
storeBizStatus,
|
|
346
|
+
selectedEligibleProducts.length,
|
|
347
|
+
],
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const storeStatusRows = [
|
|
351
|
+
{
|
|
352
|
+
label: 'Business status',
|
|
353
|
+
value: remoteConnected
|
|
354
|
+
? integrationItem?.biz_status_label ||
|
|
355
|
+
statusLabelMap[storeBizStatus] ||
|
|
356
|
+
'Indefinido'
|
|
357
|
+
: needsReconnect
|
|
358
|
+
? 'Reconexão necessária'
|
|
359
|
+
: connected
|
|
360
|
+
? 'Aguardando sync'
|
|
361
|
+
: 'Não conectado',
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
label: 'Substatus',
|
|
365
|
+
value: remoteConnected
|
|
366
|
+
? integrationItem?.sub_biz_status_label ||
|
|
367
|
+
subStatusLabelMap[storeSubStatus] ||
|
|
368
|
+
'Indefinido'
|
|
369
|
+
: '-',
|
|
370
|
+
},
|
|
371
|
+
{label: 'Menus remotos', value: String(menuCount)},
|
|
372
|
+
{label: 'Itens remotos', value: String(menuItemCount)},
|
|
373
|
+
{label: 'Produtos publicados', value: String(publishedProductCount)},
|
|
374
|
+
{label: 'Itens remotos sem local', value: String(remoteOnlyItemCount)},
|
|
375
|
+
{label: 'Areas de entrega', value: String(deliveryAreaCount)},
|
|
376
|
+
{label: 'Ultimo task ID', value: lastMenuTaskId || '-'},
|
|
377
|
+
{label: 'Status da task', value: lastMenuTaskStatus || '-'},
|
|
378
|
+
{
|
|
379
|
+
label: 'Ultima verificacao da task',
|
|
380
|
+
value: lastMenuTaskCheckedAt || 'Ainda nao verificada',
|
|
381
|
+
wide: true,
|
|
382
|
+
small: true,
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
label: 'Ultima sincronizacao',
|
|
386
|
+
value: lastSyncAt || 'Ainda nao sincronizado',
|
|
387
|
+
wide: true,
|
|
388
|
+
small: true,
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
label: 'Ultimo webhook recebido',
|
|
392
|
+
value: lastWebhookReceivedAt || 'Ainda nao recebido',
|
|
393
|
+
wide: true,
|
|
394
|
+
small: true,
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
label: 'Ultima reconciliacao',
|
|
398
|
+
value: lastReconcileAt || 'Ainda nao reconciliado',
|
|
399
|
+
wide: true,
|
|
400
|
+
small: true,
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
const settingsSummaryRows = [
|
|
405
|
+
{label: 'Raio atual', value: currentDeliveryRadius},
|
|
406
|
+
{label: 'Metodo atual', value: currentDeliveryMethod},
|
|
407
|
+
{label: 'Abertura atual', value: currentOpenTime},
|
|
408
|
+
{label: 'Fechamento atual', value: currentCloseTime},
|
|
409
|
+
{
|
|
410
|
+
label: 'Carteira de repasse',
|
|
411
|
+
value: currentSettlementWalletLabel,
|
|
412
|
+
wide: true,
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
label: 'Metodo de confirmacao atual',
|
|
416
|
+
value: currentConfirmMethod,
|
|
417
|
+
wide: true,
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
label: 'Fonte dos valores atuais',
|
|
421
|
+
value: settingsSourceLabel,
|
|
422
|
+
wide: true,
|
|
423
|
+
small: true,
|
|
424
|
+
},
|
|
425
|
+
];
|
|
426
|
+
|
|
427
|
+
const tabCounts = {
|
|
428
|
+
all: products.length,
|
|
429
|
+
eligible: products.filter(product => product.eligible).length,
|
|
430
|
+
selected: selectedEligibleProducts.length,
|
|
431
|
+
blocked: products.filter(product => !product.eligible).length,
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const hydrateSelection = useCallback(productList => {
|
|
435
|
+
if (hasHydratedSelection.current) return;
|
|
436
|
+
|
|
437
|
+
const preselected = productList
|
|
438
|
+
.filter(
|
|
439
|
+
product => product?.eligible && Boolean(product.published_remotely),
|
|
440
|
+
)
|
|
441
|
+
.map(product => String(product.id));
|
|
442
|
+
|
|
443
|
+
if (preselected.length > 0) {
|
|
444
|
+
setSelectedProductIds(preselected);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
hasHydratedSelection.current = true;
|
|
448
|
+
}, []);
|
|
449
|
+
|
|
450
|
+
const syncSelectionWithPublishedProducts = useCallback(productList => {
|
|
451
|
+
const publishedIds = (Array.isArray(productList) ? productList : [])
|
|
452
|
+
.filter(product => product?.eligible && product?.published_remotely)
|
|
453
|
+
.map(product => String(product.id));
|
|
454
|
+
|
|
455
|
+
setSelectedProductIds(publishedIds);
|
|
456
|
+
hasHydratedSelection.current = true;
|
|
457
|
+
}, []);
|
|
458
|
+
|
|
459
|
+
const applyDetailResponse = useCallback(
|
|
460
|
+
(detailResponse, {syncPublishedSelection = false} = {}) => {
|
|
461
|
+
setIntegrationItem(detailResponse?.integration || null);
|
|
462
|
+
setProductsResponse(detailResponse?.products || null);
|
|
463
|
+
|
|
464
|
+
if (syncPublishedSelection) {
|
|
465
|
+
syncSelectionWithPublishedProducts(
|
|
466
|
+
detailResponse?.products?.products || [],
|
|
467
|
+
);
|
|
468
|
+
} else {
|
|
469
|
+
hydrateSelection(detailResponse?.products?.products || []);
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
[hydrateSelection, syncSelectionWithPublishedProducts],
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
const applyStoreSettingsResponse = useCallback(response => {
|
|
476
|
+
const settings = response?.settings || {};
|
|
477
|
+
|
|
478
|
+
setStoreSettingsResponse(response || null);
|
|
479
|
+
setStoreSettingsDraft({
|
|
480
|
+
deliveryRadiusKm:
|
|
481
|
+
settings?.delivery_radius === null ||
|
|
482
|
+
settings?.delivery_radius === undefined
|
|
483
|
+
? ''
|
|
484
|
+
: String(settings.delivery_radius),
|
|
485
|
+
openTime: settings?.open_time
|
|
486
|
+
? sanitizeTimeInput(String(settings.open_time))
|
|
487
|
+
: '',
|
|
488
|
+
closeTime: settings?.close_time
|
|
489
|
+
? sanitizeTimeInput(String(settings.close_time))
|
|
490
|
+
: '',
|
|
491
|
+
deliveryMethod: normalizeDeliveryMethodCode(settings?.delivery_method),
|
|
492
|
+
confirmMethod: settings?.confirm_method
|
|
493
|
+
? sanitizeConfirmMethodInput(String(settings.confirm_method))
|
|
494
|
+
: '',
|
|
495
|
+
deliveryAreaId: settings?.delivery_area_id
|
|
496
|
+
? String(settings.delivery_area_id)
|
|
497
|
+
: '',
|
|
498
|
+
settlementWalletId: settings?.settlement_wallet_id
|
|
499
|
+
? String(settings.settlement_wallet_id)
|
|
500
|
+
: '',
|
|
501
|
+
});
|
|
502
|
+
}, []);
|
|
503
|
+
|
|
504
|
+
const loadData = useCallback(
|
|
505
|
+
async ({silent = false, refreshRemote = false} = {}) => {
|
|
506
|
+
if (!providerId) {
|
|
507
|
+
setLoading(false);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (!silent) {
|
|
512
|
+
setLoading(true);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
try {
|
|
516
|
+
const detailParams = {provider_id: providerId};
|
|
517
|
+
if (refreshRemote) detailParams.refresh_remote = 1;
|
|
518
|
+
|
|
519
|
+
const walletLoadPromise = walletStore.actions?.getItems
|
|
520
|
+
? walletStore.actions
|
|
521
|
+
.getItems({
|
|
522
|
+
people: providerId,
|
|
523
|
+
})
|
|
524
|
+
.catch(() => null)
|
|
525
|
+
: Promise.resolve(null);
|
|
526
|
+
|
|
527
|
+
const [detailResponse, settingsResponse] = await Promise.all([
|
|
528
|
+
api.fetch('/marketplace/integrations/99food/detail', {
|
|
529
|
+
params: detailParams,
|
|
530
|
+
}),
|
|
531
|
+
api.fetch('/marketplace/integrations/99food/store/settings', {
|
|
532
|
+
params: {provider_id: providerId},
|
|
533
|
+
}),
|
|
534
|
+
walletLoadPromise,
|
|
535
|
+
]);
|
|
536
|
+
|
|
537
|
+
applyDetailResponse(detailResponse);
|
|
538
|
+
applyStoreSettingsResponse(settingsResponse);
|
|
539
|
+
} catch (error) {
|
|
540
|
+
showError(formatFood99ApiError(error));
|
|
541
|
+
} finally {
|
|
542
|
+
setLoading(false);
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
[
|
|
546
|
+
applyDetailResponse,
|
|
547
|
+
applyStoreSettingsResponse,
|
|
548
|
+
providerId,
|
|
549
|
+
showError,
|
|
550
|
+
walletStore.actions,
|
|
551
|
+
],
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
const fetchMenuTaskStatus = useCallback(
|
|
555
|
+
async (taskId, {poll = false} = {}) => {
|
|
556
|
+
if (!providerId || !taskId) return null;
|
|
557
|
+
|
|
558
|
+
let lastResponse = null;
|
|
559
|
+
const maxAttempts = poll ? 6 : 1;
|
|
560
|
+
|
|
561
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
562
|
+
const response = await api.fetch(
|
|
563
|
+
`/marketplace/integrations/99food/menu/task/${taskId}`,
|
|
564
|
+
{
|
|
565
|
+
params: {provider_id: providerId},
|
|
566
|
+
},
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
lastResponse = response;
|
|
570
|
+
|
|
571
|
+
if (response?.integration || response?.products) {
|
|
572
|
+
applyDetailResponse(
|
|
573
|
+
{
|
|
574
|
+
integration: response?.integration,
|
|
575
|
+
products: response?.products,
|
|
576
|
+
},
|
|
577
|
+
{syncPublishedSelection: response?.publish_state === 'published'},
|
|
578
|
+
);
|
|
579
|
+
} else {
|
|
580
|
+
await loadData({silent: true});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (
|
|
584
|
+
['published', 'failed', 'sync_error'].includes(
|
|
585
|
+
response?.publish_state,
|
|
586
|
+
)
|
|
587
|
+
) {
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (!poll || attempt === maxAttempts - 1) {
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
await wait(3000);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return lastResponse;
|
|
599
|
+
},
|
|
600
|
+
[applyDetailResponse, loadData, providerId],
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
useFocusEffect(
|
|
604
|
+
useCallback(() => {
|
|
605
|
+
if (!providerId || hasLoadedOnceRef.current) {
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
hasLoadedOnceRef.current = true;
|
|
610
|
+
loadData();
|
|
611
|
+
|
|
612
|
+
return undefined;
|
|
613
|
+
}, [loadData, providerId]),
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
const onRefresh = useCallback(async () => {
|
|
617
|
+
setRefreshing(true);
|
|
618
|
+
try {
|
|
619
|
+
if (lastMenuTaskId) {
|
|
620
|
+
await fetchMenuTaskStatus(lastMenuTaskId, {poll: false});
|
|
621
|
+
} else {
|
|
622
|
+
await loadData({silent: true, refreshRemote: true});
|
|
623
|
+
}
|
|
624
|
+
} catch (error) {
|
|
625
|
+
showError(formatFood99ApiError(error));
|
|
626
|
+
} finally {
|
|
627
|
+
setRefreshing(false);
|
|
628
|
+
}
|
|
629
|
+
}, [fetchMenuTaskStatus, lastMenuTaskId, loadData, showError]);
|
|
630
|
+
|
|
631
|
+
const withAction = useCallback(async (key, action) => {
|
|
632
|
+
setActionLoading(key);
|
|
633
|
+
try {
|
|
634
|
+
await action();
|
|
635
|
+
} finally {
|
|
636
|
+
setActionLoading('');
|
|
637
|
+
}
|
|
638
|
+
}, []);
|
|
639
|
+
|
|
640
|
+
const handleToggleProduct = useCallback(
|
|
641
|
+
product => {
|
|
642
|
+
if (!product?.eligible) {
|
|
643
|
+
showError(
|
|
644
|
+
product?.blockers?.[0] || 'Produto indisponivel para a integracao.',
|
|
645
|
+
);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
setSelectedProductIds(currentIds => {
|
|
650
|
+
const productId = String(product.id);
|
|
651
|
+
if (currentIds.includes(productId)) {
|
|
652
|
+
return currentIds.filter(id => id !== productId);
|
|
653
|
+
}
|
|
654
|
+
return [...currentIds, productId];
|
|
655
|
+
});
|
|
656
|
+
},
|
|
657
|
+
[showError],
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
const markNextProductCardPressAsHandled = useCallback(() => {
|
|
661
|
+
ignoreNextProductCardPressRef.current = true;
|
|
662
|
+
|
|
663
|
+
const release = () => {
|
|
664
|
+
ignoreNextProductCardPressRef.current = false;
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
668
|
+
requestAnimationFrame(release);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
setTimeout(release, 0);
|
|
673
|
+
}, []);
|
|
674
|
+
|
|
675
|
+
const handleProductCardPress = useCallback(
|
|
676
|
+
product => {
|
|
677
|
+
if (ignoreNextProductCardPressRef.current) {
|
|
678
|
+
ignoreNextProductCardPressRef.current = false;
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
handleToggleProduct(product);
|
|
683
|
+
},
|
|
684
|
+
[handleToggleProduct],
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
const handleConnectStore = useCallback(async () => {
|
|
688
|
+
if (!providerId) return;
|
|
689
|
+
|
|
690
|
+
await withAction('connect', async () => {
|
|
691
|
+
try {
|
|
692
|
+
const response = await api.fetch(
|
|
693
|
+
'/marketplace/integrations/99food/store/authorization-page',
|
|
694
|
+
{
|
|
695
|
+
method: 'POST',
|
|
696
|
+
body: {provider_id: providerId},
|
|
697
|
+
},
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
const authUrl =
|
|
701
|
+
response?.data?.url ||
|
|
702
|
+
response?.data?.auth_url ||
|
|
703
|
+
response?.data?.authorization_url ||
|
|
704
|
+
response?.data?.authorization_page ||
|
|
705
|
+
response?.data?.web_page_url ||
|
|
706
|
+
null;
|
|
707
|
+
|
|
708
|
+
if (authUrl) {
|
|
709
|
+
await openAuthorizationUrl(authUrl);
|
|
710
|
+
showInfo('Abrindo fluxo de integracao da loja.');
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (isErrnoSuccess(response?.errno)) {
|
|
715
|
+
showSuccess('Solicitacao de integracao enviada.');
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
showError(
|
|
720
|
+
response?.errmsg || 'Nao foi possivel iniciar a integracao da loja.',
|
|
721
|
+
);
|
|
722
|
+
} catch (error) {
|
|
723
|
+
showError(formatFood99ApiError(error));
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}, [providerId, showError, showInfo, showSuccess, withAction]);
|
|
727
|
+
|
|
728
|
+
const handleStoreStatusChange = useCallback(
|
|
729
|
+
async nextStatus => {
|
|
730
|
+
if (!providerId) return;
|
|
731
|
+
|
|
732
|
+
await withAction(nextStatus === 1 ? 'online' : 'offline', async () => {
|
|
733
|
+
try {
|
|
734
|
+
const response = await api.fetch(
|
|
735
|
+
'/marketplace/integrations/99food/store/status',
|
|
736
|
+
{
|
|
737
|
+
method: 'POST',
|
|
738
|
+
body: {
|
|
739
|
+
provider_id: providerId,
|
|
740
|
+
biz_status: nextStatus,
|
|
741
|
+
auto_switch: 2,
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
if (!isErrnoSuccess(response?.errno)) {
|
|
747
|
+
showError(
|
|
748
|
+
response?.errmsg ||
|
|
749
|
+
'Nao foi possivel atualizar o status da loja.',
|
|
750
|
+
);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
showSuccess(
|
|
755
|
+
nextStatus === 1
|
|
756
|
+
? 'Loja enviada para online.'
|
|
757
|
+
: 'Loja enviada para offline.',
|
|
758
|
+
);
|
|
759
|
+
await loadData({silent: true});
|
|
760
|
+
} catch (error) {
|
|
761
|
+
showError(formatFood99ApiError(error));
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
},
|
|
765
|
+
[loadData, providerId, showError, showSuccess, withAction],
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
const getFirstOperationError = useCallback(response => {
|
|
769
|
+
if (!response || typeof response !== 'object') return null;
|
|
770
|
+
|
|
771
|
+
if (response?.result && !isErrnoSuccess(response.result?.errno)) {
|
|
772
|
+
return response.result?.errmsg || 'A operacao falhou na 99Food.';
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const operations = response?.operations || {};
|
|
776
|
+
for (const operationKey of Object.keys(operations)) {
|
|
777
|
+
const operationResult = operations?.[operationKey];
|
|
778
|
+
if (!isErrnoSuccess(operationResult?.errno)) {
|
|
779
|
+
return operationResult?.errmsg || `Falha em ${operationKey}.`;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return null;
|
|
784
|
+
}, []);
|
|
785
|
+
|
|
786
|
+
const handleSaveStoreSettings = useCallback(async () => {
|
|
787
|
+
if (!providerId) return;
|
|
788
|
+
|
|
789
|
+
await withAction('save-settings', async () => {
|
|
790
|
+
try {
|
|
791
|
+
const deliveryRadius = String(
|
|
792
|
+
storeSettingsDraft?.deliveryRadiusKm || '',
|
|
793
|
+
).trim();
|
|
794
|
+
const openTime = String(storeSettingsDraft?.openTime || '').trim();
|
|
795
|
+
const closeTime = String(storeSettingsDraft?.closeTime || '').trim();
|
|
796
|
+
const deliveryMethod = String(
|
|
797
|
+
storeSettingsDraft?.deliveryMethod || '',
|
|
798
|
+
).trim();
|
|
799
|
+
const confirmMethod = String(
|
|
800
|
+
storeSettingsDraft?.confirmMethod || '',
|
|
801
|
+
).trim();
|
|
802
|
+
const deliveryAreaId = String(
|
|
803
|
+
storeSettingsDraft?.deliveryAreaId || '',
|
|
804
|
+
).trim();
|
|
805
|
+
const settlementWalletId = String(
|
|
806
|
+
storeSettingsDraft?.settlementWalletId || '',
|
|
807
|
+
).trim();
|
|
808
|
+
|
|
809
|
+
if (deliveryRadius && !/^\d+(\.\d{1,2})?$/.test(deliveryRadius)) {
|
|
810
|
+
showError(
|
|
811
|
+
'Raio de atendimento invalido. Use numero positivo (ex.: 5 ou 5.5).',
|
|
812
|
+
);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (
|
|
817
|
+
(openTime || closeTime) &&
|
|
818
|
+
(!isValidTimeInput(openTime) || !isValidTimeInput(closeTime))
|
|
819
|
+
) {
|
|
820
|
+
showError('Horario invalido. Use o formato HH:mm (ex.: 08:00).');
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (deliveryMethod && !['1', '2'].includes(deliveryMethod)) {
|
|
825
|
+
showError(
|
|
826
|
+
'Metodo de entrega invalido. Selecione Loja (2) ou Entrega 99 (1).',
|
|
827
|
+
);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (confirmMethod && !/^\d{1,3}$/.test(confirmMethod)) {
|
|
832
|
+
showError('Metodo de confirmacao invalido. Informe apenas numeros.');
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (!settlementWalletId) {
|
|
837
|
+
showError('Selecione a carteira de repasse da loja.');
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const walletMatch = wallets.some(
|
|
842
|
+
wallet => String(wallet.id) === settlementWalletId,
|
|
843
|
+
);
|
|
844
|
+
if (!walletMatch) {
|
|
845
|
+
showError('Selecione uma carteira valida da empresa ativa.');
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const payload = {provider_id: providerId};
|
|
850
|
+
if (deliveryRadius) payload.delivery_radius_km = deliveryRadius;
|
|
851
|
+
if (openTime) payload.open_time = openTime;
|
|
852
|
+
if (closeTime) payload.close_time = closeTime;
|
|
853
|
+
if (deliveryMethod) payload.delivery_method = deliveryMethod;
|
|
854
|
+
if (confirmMethod) payload.confirm_method = confirmMethod;
|
|
855
|
+
if (deliveryAreaId) payload.delivery_area_id = deliveryAreaId;
|
|
856
|
+
payload.settlement_wallet_id = settlementWalletId;
|
|
857
|
+
|
|
858
|
+
const response = await api.fetch(
|
|
859
|
+
'/marketplace/integrations/99food/store/settings',
|
|
860
|
+
{
|
|
861
|
+
method: 'POST',
|
|
862
|
+
body: payload,
|
|
863
|
+
},
|
|
864
|
+
);
|
|
865
|
+
|
|
866
|
+
const operationError = getFirstOperationError(response);
|
|
867
|
+
if (response?.error || operationError) {
|
|
868
|
+
showError(
|
|
869
|
+
response?.error ||
|
|
870
|
+
operationError ||
|
|
871
|
+
'Nao foi possivel salvar as configuracoes.',
|
|
872
|
+
);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
applyStoreSettingsResponse(response);
|
|
877
|
+
await loadData({silent: true});
|
|
878
|
+
showSuccess('Configuracoes da loja atualizadas.');
|
|
879
|
+
} catch (error) {
|
|
880
|
+
showError(formatFood99ApiError(error));
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
}, [
|
|
884
|
+
applyStoreSettingsResponse,
|
|
885
|
+
getFirstOperationError,
|
|
886
|
+
loadData,
|
|
887
|
+
providerId,
|
|
888
|
+
showError,
|
|
889
|
+
showSuccess,
|
|
890
|
+
storeSettingsDraft,
|
|
891
|
+
wallets,
|
|
892
|
+
withAction,
|
|
893
|
+
]);
|
|
894
|
+
|
|
895
|
+
const openQuickWalletModal = useCallback(() => {
|
|
896
|
+
setQuickWalletName('');
|
|
897
|
+
setQuickWalletModalVisible(true);
|
|
898
|
+
}, []);
|
|
899
|
+
|
|
900
|
+
const closeQuickWalletModal = useCallback(() => {
|
|
901
|
+
setQuickWalletModalVisible(false);
|
|
902
|
+
setQuickWalletName('');
|
|
903
|
+
}, []);
|
|
904
|
+
|
|
905
|
+
const handleCreateQuickWallet = useCallback(async () => {
|
|
906
|
+
if (!providerId) {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
const walletName = String(quickWalletName || '').trim();
|
|
911
|
+
if (!walletName) {
|
|
912
|
+
showError('Informe o nome da carteira.');
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
await withAction('create-wallet', async () => {
|
|
917
|
+
try {
|
|
918
|
+
const savedWallet = await walletStore.actions.save({
|
|
919
|
+
people: `/people/${providerId}`,
|
|
920
|
+
wallet: walletName,
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
const savedWalletId = String(savedWallet?.id || '').trim();
|
|
924
|
+
const resolvedWallet =
|
|
925
|
+
(Array.isArray(walletStore.getters.items)
|
|
926
|
+
? walletStore.getters.items.find(wallet => {
|
|
927
|
+
return (
|
|
928
|
+
String(wallet?.wallet || '')
|
|
929
|
+
.trim()
|
|
930
|
+
.toLowerCase() === walletName.toLowerCase()
|
|
931
|
+
);
|
|
932
|
+
})
|
|
933
|
+
: null) || savedWallet;
|
|
934
|
+
const resolvedWalletId = String(
|
|
935
|
+
savedWalletId || resolvedWallet?.id || '',
|
|
936
|
+
).trim();
|
|
937
|
+
|
|
938
|
+
if (resolvedWalletId) {
|
|
939
|
+
setStoreSettingsDraft(current => ({
|
|
940
|
+
...current,
|
|
941
|
+
settlementWalletId: resolvedWalletId,
|
|
942
|
+
}));
|
|
943
|
+
showSuccess('Carteira criada e selecionada no repasse.');
|
|
944
|
+
} else {
|
|
945
|
+
showSuccess('Carteira criada. Selecione-a no repasse.');
|
|
946
|
+
}
|
|
947
|
+
closeQuickWalletModal();
|
|
948
|
+
} catch (error) {
|
|
949
|
+
showError(formatFood99ApiError(error));
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
}, [
|
|
953
|
+
closeQuickWalletModal,
|
|
954
|
+
formatFood99ApiError,
|
|
955
|
+
providerId,
|
|
956
|
+
quickWalletName,
|
|
957
|
+
setStoreSettingsDraft,
|
|
958
|
+
showError,
|
|
959
|
+
showSuccess,
|
|
960
|
+
walletStore.actions,
|
|
961
|
+
withAction,
|
|
962
|
+
]);
|
|
963
|
+
|
|
964
|
+
const handleManualBindStore = useCallback(async () => {
|
|
965
|
+
if (!providerId) return;
|
|
966
|
+
|
|
967
|
+
const shopId = String(manualShopId || '').trim();
|
|
968
|
+
if (!shopId) {
|
|
969
|
+
showError('Informe o shop_id para vincular a loja.');
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
await withAction('bind-manual', async () => {
|
|
974
|
+
try {
|
|
975
|
+
const response = await api.fetch(
|
|
976
|
+
'/marketplace/integrations/99food/store/connect',
|
|
977
|
+
{
|
|
978
|
+
method: 'POST',
|
|
979
|
+
body: {
|
|
980
|
+
provider_id: providerId,
|
|
981
|
+
shop_id: shopId,
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
const operationError = getFirstOperationError(response);
|
|
987
|
+
if (response?.error || operationError) {
|
|
988
|
+
showError(
|
|
989
|
+
response?.error ||
|
|
990
|
+
operationError ||
|
|
991
|
+
'Nao foi possivel vincular a loja.',
|
|
992
|
+
);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
applyStoreSettingsResponse(response);
|
|
997
|
+
await loadData({silent: true});
|
|
998
|
+
showSuccess('Loja vinculada manualmente com sucesso.');
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
showError(formatFood99ApiError(error));
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
}, [
|
|
1004
|
+
applyStoreSettingsResponse,
|
|
1005
|
+
getFirstOperationError,
|
|
1006
|
+
loadData,
|
|
1007
|
+
manualShopId,
|
|
1008
|
+
providerId,
|
|
1009
|
+
showError,
|
|
1010
|
+
showSuccess,
|
|
1011
|
+
withAction,
|
|
1012
|
+
]);
|
|
1013
|
+
|
|
1014
|
+
const handleDisconnectStore = useCallback(async () => {
|
|
1015
|
+
if (!providerId) return;
|
|
1016
|
+
|
|
1017
|
+
await withAction('disconnect', async () => {
|
|
1018
|
+
try {
|
|
1019
|
+
const response = await api.fetch(
|
|
1020
|
+
'/marketplace/integrations/99food/store/disconnect',
|
|
1021
|
+
{
|
|
1022
|
+
method: 'POST',
|
|
1023
|
+
body: {provider_id: providerId},
|
|
1024
|
+
},
|
|
1025
|
+
);
|
|
1026
|
+
|
|
1027
|
+
const operationError = getFirstOperationError(response);
|
|
1028
|
+
if (response?.error || operationError) {
|
|
1029
|
+
showError(
|
|
1030
|
+
response?.error ||
|
|
1031
|
+
operationError ||
|
|
1032
|
+
'Nao foi possivel desconectar a loja.',
|
|
1033
|
+
);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
setManualShopId('');
|
|
1038
|
+
applyStoreSettingsResponse(response);
|
|
1039
|
+
await loadData({silent: true});
|
|
1040
|
+
showSuccess('Loja desconectada da 99Food.');
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
showError(formatFood99ApiError(error));
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
}, [
|
|
1046
|
+
applyStoreSettingsResponse,
|
|
1047
|
+
getFirstOperationError,
|
|
1048
|
+
loadData,
|
|
1049
|
+
providerId,
|
|
1050
|
+
showError,
|
|
1051
|
+
showSuccess,
|
|
1052
|
+
withAction,
|
|
1053
|
+
]);
|
|
1054
|
+
|
|
1055
|
+
const handlePreview = useCallback(async () => {
|
|
1056
|
+
if (!providerId) return;
|
|
1057
|
+
|
|
1058
|
+
if (selectedEligibleProducts.length < MINIMUM_REQUIRED_ITEMS) {
|
|
1059
|
+
showError(
|
|
1060
|
+
`Selecione pelo menos ${MINIMUM_REQUIRED_ITEMS} produtos elegiveis para publicar.`,
|
|
1061
|
+
);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
setPreviewLoading(true);
|
|
1066
|
+
try {
|
|
1067
|
+
const response = await api.fetch(
|
|
1068
|
+
'/marketplace/integrations/99food/menu/preview',
|
|
1069
|
+
{
|
|
1070
|
+
method: 'POST',
|
|
1071
|
+
body: {
|
|
1072
|
+
provider_id: providerId,
|
|
1073
|
+
product_ids: selectedEligibleProducts.map(product => product.id),
|
|
1074
|
+
},
|
|
1075
|
+
},
|
|
1076
|
+
);
|
|
1077
|
+
|
|
1078
|
+
if (Array.isArray(response?.errors) && response.errors.length > 0) {
|
|
1079
|
+
showError(response.errors[0]);
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
setPreviewData(response);
|
|
1084
|
+
setPreviewVisible(true);
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
showError(formatFood99ApiError(error));
|
|
1087
|
+
} finally {
|
|
1088
|
+
setPreviewLoading(false);
|
|
1089
|
+
}
|
|
1090
|
+
}, [providerId, selectedEligibleProducts, showError]);
|
|
1091
|
+
|
|
1092
|
+
const handleUpload = useCallback(async () => {
|
|
1093
|
+
if (!providerId) return;
|
|
1094
|
+
|
|
1095
|
+
setUploading(true);
|
|
1096
|
+
try {
|
|
1097
|
+
const response = await api.fetch(
|
|
1098
|
+
'/marketplace/integrations/99food/menu/upload',
|
|
1099
|
+
{
|
|
1100
|
+
method: 'POST',
|
|
1101
|
+
body: {
|
|
1102
|
+
provider_id: providerId,
|
|
1103
|
+
product_ids: selectedEligibleProducts.map(product => product.id),
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1106
|
+
);
|
|
1107
|
+
|
|
1108
|
+
if (!isErrnoSuccess(response?.result?.errno)) {
|
|
1109
|
+
showError(
|
|
1110
|
+
response?.result?.errmsg ||
|
|
1111
|
+
'Nao foi possivel publicar o menu no 99Food.',
|
|
1112
|
+
);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
setLastUploadResult(response);
|
|
1117
|
+
if (response?.integration || response?.products) {
|
|
1118
|
+
applyDetailResponse({
|
|
1119
|
+
integration: response?.integration,
|
|
1120
|
+
products: response?.products,
|
|
1121
|
+
});
|
|
1122
|
+
} else {
|
|
1123
|
+
await loadData({silent: true});
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
showInfo('Cardapio enviado para processamento na 99Food.');
|
|
1127
|
+
setPreviewVisible(false);
|
|
1128
|
+
|
|
1129
|
+
const taskId =
|
|
1130
|
+
normalizeTaskId(response?.integration?.last_menu_task_id) ||
|
|
1131
|
+
normalizeTaskId(response?.result?.data?.taskID) ||
|
|
1132
|
+
normalizeTaskId(response?.result?.data?.taskId);
|
|
1133
|
+
|
|
1134
|
+
if (taskId) {
|
|
1135
|
+
const taskResponse = await fetchMenuTaskStatus(taskId, {poll: true});
|
|
1136
|
+
|
|
1137
|
+
if (taskResponse?.publish_state === 'published') {
|
|
1138
|
+
showSuccess('Cardapio publicado com sucesso na 99Food.');
|
|
1139
|
+
} else if (taskResponse?.publish_state === 'failed') {
|
|
1140
|
+
showError(
|
|
1141
|
+
taskResponse?.task_message ||
|
|
1142
|
+
'A publicacao do cardapio falhou na 99Food.',
|
|
1143
|
+
);
|
|
1144
|
+
} else if (taskResponse?.publish_state === 'sync_error') {
|
|
1145
|
+
showError(
|
|
1146
|
+
taskResponse?.task_message ||
|
|
1147
|
+
'A task foi concluida, mas o catalogo remoto ainda nao foi confirmado.',
|
|
1148
|
+
);
|
|
1149
|
+
} else {
|
|
1150
|
+
showInfo(
|
|
1151
|
+
'A publicacao do cardapio segue em processamento na 99Food.',
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
showError(formatFood99ApiError(error));
|
|
1157
|
+
} finally {
|
|
1158
|
+
setUploading(false);
|
|
1159
|
+
}
|
|
1160
|
+
}, [
|
|
1161
|
+
applyDetailResponse,
|
|
1162
|
+
fetchMenuTaskStatus,
|
|
1163
|
+
loadData,
|
|
1164
|
+
providerId,
|
|
1165
|
+
selectedEligibleProducts,
|
|
1166
|
+
showError,
|
|
1167
|
+
showInfo,
|
|
1168
|
+
showSuccess,
|
|
1169
|
+
]);
|
|
1170
|
+
|
|
1171
|
+
const handleRefreshStoreStatus = useCallback(async () => {
|
|
1172
|
+
await withAction('refresh', async () => {
|
|
1173
|
+
if (lastMenuTaskId) {
|
|
1174
|
+
await fetchMenuTaskStatus(lastMenuTaskId, {poll: false});
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
await loadData({silent: true});
|
|
1178
|
+
});
|
|
1179
|
+
}, [fetchMenuTaskStatus, lastMenuTaskId, loadData, withAction]);
|
|
1180
|
+
|
|
1181
|
+
const handleSyncOrders = useCallback(async () => {
|
|
1182
|
+
if (!providerId) return;
|
|
1183
|
+
|
|
1184
|
+
await withAction('sync-orders', async () => {
|
|
1185
|
+
try {
|
|
1186
|
+
const response = await api.fetch(
|
|
1187
|
+
'/marketplace/integrations/99food/orders/sync',
|
|
1188
|
+
{
|
|
1189
|
+
method: 'POST',
|
|
1190
|
+
body: {
|
|
1191
|
+
provider_id: providerId,
|
|
1192
|
+
},
|
|
1193
|
+
},
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
if (!isErrnoSuccess(response?.errno)) {
|
|
1197
|
+
showError(
|
|
1198
|
+
response?.errmsg ||
|
|
1199
|
+
'Nao foi possivel sincronizar os pedidos da 99Food.',
|
|
1200
|
+
);
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const processedCount = Number(
|
|
1205
|
+
response?.data?.processed_order_count || 0,
|
|
1206
|
+
);
|
|
1207
|
+
const failedCount = Number(response?.data?.failed_order_count || 0);
|
|
1208
|
+
const acknowledgedCount = Number(
|
|
1209
|
+
response?.data?.acknowledged_event_count || 0,
|
|
1210
|
+
);
|
|
1211
|
+
const failedAckCount = Number(
|
|
1212
|
+
response?.data?.failed_acknowledged_count || 0,
|
|
1213
|
+
);
|
|
1214
|
+
const ackSummary =
|
|
1215
|
+
acknowledgedCount > 0
|
|
1216
|
+
? `, ${acknowledgedCount} evento(s) reconhecido(s)`
|
|
1217
|
+
: '';
|
|
1218
|
+
|
|
1219
|
+
if (processedCount > 0) {
|
|
1220
|
+
if (failedCount > 0 || failedAckCount > 0) {
|
|
1221
|
+
showInfo(
|
|
1222
|
+
`Sincronizacao executada com pendencias: ${processedCount} pedido(s) importado(s), ${failedCount} falha(s)${ackSummary}.`,
|
|
1223
|
+
);
|
|
1224
|
+
} else {
|
|
1225
|
+
showSuccess(
|
|
1226
|
+
`Sincronizacao concluida: ${processedCount} pedido(s) importado(s)${ackSummary}.`,
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
} else {
|
|
1230
|
+
showInfo(
|
|
1231
|
+
`Sincronizacao executada, mas nenhum pedido novo foi importado${ackSummary}.`,
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
await loadData({silent: true});
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
showError(formatFood99ApiError(error));
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
}, [loadData, providerId, showError, showInfo, showSuccess, withAction]);
|
|
1241
|
+
|
|
1242
|
+
const handleSyncTodayHistory = useCallback(async () => {
|
|
1243
|
+
if (!providerId) return;
|
|
1244
|
+
|
|
1245
|
+
await withAction('sync-history', async () => {
|
|
1246
|
+
try {
|
|
1247
|
+
const fromTime = resolveFood99HistoryFromTime();
|
|
1248
|
+
const response = await api.fetch(
|
|
1249
|
+
'/marketplace/integrations/99food/orders/sync',
|
|
1250
|
+
{
|
|
1251
|
+
method: 'POST',
|
|
1252
|
+
body: {
|
|
1253
|
+
provider_id: providerId,
|
|
1254
|
+
...(fromTime ? {from_time: fromTime} : {}),
|
|
1255
|
+
},
|
|
1256
|
+
},
|
|
1257
|
+
);
|
|
1258
|
+
|
|
1259
|
+
if (!isErrnoSuccess(response?.errno)) {
|
|
1260
|
+
showError(
|
|
1261
|
+
response?.errmsg ||
|
|
1262
|
+
'Nao foi possivel sincronizar o historico de pedidos da 99Food.',
|
|
1263
|
+
);
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const processedCount = Number(
|
|
1268
|
+
response?.data?.processed_order_count || 0,
|
|
1269
|
+
);
|
|
1270
|
+
const failedCount = Number(response?.data?.failed_order_count || 0);
|
|
1271
|
+
const acknowledgedCount = Number(
|
|
1272
|
+
response?.data?.acknowledged_event_count || 0,
|
|
1273
|
+
);
|
|
1274
|
+
const failedAckCount = Number(
|
|
1275
|
+
response?.data?.failed_acknowledged_count || 0,
|
|
1276
|
+
);
|
|
1277
|
+
const ackSummary =
|
|
1278
|
+
acknowledgedCount > 0
|
|
1279
|
+
? `, ${acknowledgedCount} evento(s) reconhecido(s)`
|
|
1280
|
+
: '';
|
|
1281
|
+
|
|
1282
|
+
if (processedCount > 0) {
|
|
1283
|
+
if (failedCount > 0 || failedAckCount > 0) {
|
|
1284
|
+
showInfo(
|
|
1285
|
+
`Historico de hoje executado com pendencias: ${processedCount} pedido(s) importado(s), ${failedCount} falha(s)${ackSummary}.`,
|
|
1286
|
+
);
|
|
1287
|
+
} else {
|
|
1288
|
+
showSuccess(
|
|
1289
|
+
`Historico de hoje concluido: ${processedCount} pedido(s) importado(s)${ackSummary}.`,
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
} else {
|
|
1293
|
+
showInfo(
|
|
1294
|
+
`Historico de hoje executado, mas nenhum pedido novo foi importado${ackSummary}.`,
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
await loadData({silent: true});
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
showError(formatFood99ApiError(error));
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
}, [loadData, providerId, showError, showInfo, showSuccess, withAction]);
|
|
1304
|
+
|
|
1305
|
+
const handleLoadRemoteMenu = useCallback(async () => {
|
|
1306
|
+
if (!providerId) return;
|
|
1307
|
+
|
|
1308
|
+
setRemoteMenuError('');
|
|
1309
|
+
await withAction('remote-menu', async () => {
|
|
1310
|
+
try {
|
|
1311
|
+
const response = await api.fetch(
|
|
1312
|
+
'/marketplace/integrations/99food/menu/import',
|
|
1313
|
+
{
|
|
1314
|
+
method: 'POST',
|
|
1315
|
+
body: JSON.stringify({provider_id: providerId, mode: 'snapshot'}),
|
|
1316
|
+
},
|
|
1317
|
+
);
|
|
1318
|
+
const result = response?.result || {};
|
|
1319
|
+
if (String(result?.errno ?? '0') !== '0') {
|
|
1320
|
+
setRemoteMenuError(
|
|
1321
|
+
result?.errmsg ||
|
|
1322
|
+
'Nao foi possivel ler o cardapio remoto da 99Food.',
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
setRemoteMenuSnapshot(result?.data || null);
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
setRemoteMenuError(formatFood99ApiError(error));
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
}, [providerId, withAction]);
|
|
1331
|
+
|
|
1332
|
+
const selectionSummaryTone =
|
|
1333
|
+
selectedEligibleProducts.length >= MINIMUM_REQUIRED_ITEMS
|
|
1334
|
+
? brandColors.success
|
|
1335
|
+
: brandColors.warning;
|
|
1336
|
+
|
|
1337
|
+
const sectionTabs = [
|
|
1338
|
+
{key: 'overview', label: 'Resumo'},
|
|
1339
|
+
{key: 'store', label: 'Loja'},
|
|
1340
|
+
{key: 'settings', label: 'Operação'},
|
|
1341
|
+
{key: 'catalog', label: 'Cardápio', badge: selectedEligibleProducts.length},
|
|
1342
|
+
{key: 'remoteCatalog', label: 'Cardápio no 99'},
|
|
1343
|
+
];
|
|
1344
|
+
|
|
1345
|
+
if (!providerId) {
|
|
1346
|
+
return (
|
|
1347
|
+
<SafeAreaView style={styles.container} edges={['bottom']}>
|
|
1348
|
+
<View style={styles.centerState}>
|
|
1349
|
+
<Icon name="building" size={32} color={brandColors.textSecondary} />
|
|
1350
|
+
<Text style={styles.centerStateTitle}>
|
|
1351
|
+
Selecione uma empresa para continuar
|
|
1352
|
+
</Text>
|
|
1353
|
+
<Text style={styles.centerStateText}>
|
|
1354
|
+
A integração 99Food sempre trabalha com a empresa ativa no filtro.
|
|
1355
|
+
</Text>
|
|
1356
|
+
</View>
|
|
1357
|
+
</SafeAreaView>
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
if (loading) {
|
|
1362
|
+
return (
|
|
1363
|
+
<SafeAreaView style={styles.container} edges={['bottom']}>
|
|
1364
|
+
<View style={styles.centerState}>
|
|
1365
|
+
<ActivityIndicator size="large" color={brandColors.primary} />
|
|
1366
|
+
<Text style={styles.centerStateTitle}>Carregando integrações</Text>
|
|
1367
|
+
<Text style={styles.centerStateText}>
|
|
1368
|
+
Estamos buscando o status da loja e os produtos disponíveis.
|
|
1369
|
+
</Text>
|
|
1370
|
+
</View>
|
|
1371
|
+
</SafeAreaView>
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
return (
|
|
1376
|
+
<SafeAreaView
|
|
1377
|
+
style={[styles.container, {backgroundColor: brandColors.background}]}
|
|
1378
|
+
edges={['bottom']}>
|
|
1379
|
+
<ScrollView
|
|
1380
|
+
contentContainerStyle={styles.scroll}
|
|
1381
|
+
showsVerticalScrollIndicator={false}
|
|
1382
|
+
refreshControl={
|
|
1383
|
+
<RefreshControl
|
|
1384
|
+
refreshing={refreshing}
|
|
1385
|
+
onRefresh={onRefresh}
|
|
1386
|
+
tintColor={brandColors.primary}
|
|
1387
|
+
/>
|
|
1388
|
+
}>
|
|
1389
|
+
<IntegrationHero
|
|
1390
|
+
shadowStyle={integrationCardShadowStyle}
|
|
1391
|
+
accentColor={brandColors.primary}
|
|
1392
|
+
palette={heroPalette}
|
|
1393
|
+
eyebrow="Marketplace"
|
|
1394
|
+
title="Integração 99Food"
|
|
1395
|
+
description="Separei leitura geral, vínculo da loja, configuração operacional e catálogo para reduzir a complexidade da tela."
|
|
1396
|
+
logo={logo}
|
|
1397
|
+
iconName="cpu"
|
|
1398
|
+
/>
|
|
1399
|
+
|
|
1400
|
+
<IntegrationTabs
|
|
1401
|
+
tabs={sectionTabs}
|
|
1402
|
+
activeKey={activeTab}
|
|
1403
|
+
onChange={setActiveTab}
|
|
1404
|
+
accentColor={brandColors.primary}
|
|
1405
|
+
palette={brandColors}
|
|
1406
|
+
/>
|
|
1407
|
+
|
|
1408
|
+
{activeTab === 'overview' && (
|
|
1409
|
+
<Food99OverviewTab
|
|
1410
|
+
shadowStyle={integrationCardShadowStyle}
|
|
1411
|
+
palette={brandColors}
|
|
1412
|
+
currentCompany={currentCompany}
|
|
1413
|
+
providerId={providerId}
|
|
1414
|
+
connected={connected}
|
|
1415
|
+
needsReconnect={needsReconnect}
|
|
1416
|
+
summaryCards={summaryCards}
|
|
1417
|
+
lastMenuPublishState={lastMenuPublishState}
|
|
1418
|
+
publicationTone={publicationTone}
|
|
1419
|
+
publishStateLabel={getPublishStateLabel(lastMenuPublishState)}
|
|
1420
|
+
lastMenuTaskMessage={lastMenuTaskMessage}
|
|
1421
|
+
lastErrorMessage={lastErrorMessage}
|
|
1422
|
+
/>
|
|
1423
|
+
)}
|
|
1424
|
+
|
|
1425
|
+
{activeTab === 'store' && (
|
|
1426
|
+
<Food99StoreTab
|
|
1427
|
+
shadowStyle={integrationCardShadowStyle}
|
|
1428
|
+
accentColor={brandColors.primary}
|
|
1429
|
+
palette={brandColors}
|
|
1430
|
+
statusRows={storeStatusRows}
|
|
1431
|
+
lastMenuPublishState={lastMenuPublishState}
|
|
1432
|
+
publicationTone={publicationTone}
|
|
1433
|
+
publishStateLabel={getPublishStateLabel(lastMenuPublishState)}
|
|
1434
|
+
lastMenuTaskMessage={lastMenuTaskMessage}
|
|
1435
|
+
lastErrorMessage={lastErrorMessage}
|
|
1436
|
+
actionLoading={actionLoading}
|
|
1437
|
+
connected={connected}
|
|
1438
|
+
needsReconnect={needsReconnect}
|
|
1439
|
+
isOnline={isOnline}
|
|
1440
|
+
onRefresh={handleRefreshStoreStatus}
|
|
1441
|
+
onConnect={handleConnectStore}
|
|
1442
|
+
onToggleStatus={() => handleStoreStatusChange(isOnline ? 2 : 1)}
|
|
1443
|
+
onSyncOrders={handleSyncOrders}
|
|
1444
|
+
onSyncTodayHistory={handleSyncTodayHistory}
|
|
1445
|
+
manualShopId={manualShopId}
|
|
1446
|
+
setManualShopId={setManualShopId}
|
|
1447
|
+
onManualBind={handleManualBindStore}
|
|
1448
|
+
onDisconnect={handleDisconnectStore}
|
|
1449
|
+
/>
|
|
1450
|
+
)}
|
|
1451
|
+
|
|
1452
|
+
{activeTab === 'settings' && (
|
|
1453
|
+
<Food99SettingsTab
|
|
1454
|
+
shadowStyle={integrationCardShadowStyle}
|
|
1455
|
+
accentColor={brandColors.primary}
|
|
1456
|
+
palette={brandColors}
|
|
1457
|
+
settingsSummaryRows={settingsSummaryRows}
|
|
1458
|
+
storeSettingsDraft={storeSettingsDraft}
|
|
1459
|
+
setStoreSettingsDraft={setStoreSettingsDraft}
|
|
1460
|
+
wallets={wallets}
|
|
1461
|
+
walletLoading={isLoadingWallets}
|
|
1462
|
+
actionLoading={actionLoading}
|
|
1463
|
+
onQuickCreateWallet={openQuickWalletModal}
|
|
1464
|
+
onSave={handleSaveStoreSettings}
|
|
1465
|
+
/>
|
|
1466
|
+
)}
|
|
1467
|
+
|
|
1468
|
+
{activeTab === 'catalog' && (
|
|
1469
|
+
<Food99CatalogTab
|
|
1470
|
+
shadowStyle={integrationCardShadowStyle}
|
|
1471
|
+
accentColor={brandColors.primary}
|
|
1472
|
+
palette={brandColors}
|
|
1473
|
+
selectionSummaryTone={selectionSummaryTone}
|
|
1474
|
+
search={search}
|
|
1475
|
+
setSearch={setSearch}
|
|
1476
|
+
filterKey={filterKey}
|
|
1477
|
+
setFilterKey={setFilterKey}
|
|
1478
|
+
tabCounts={tabCounts}
|
|
1479
|
+
productsResponse={productsResponse}
|
|
1480
|
+
selectedEligibleProducts={selectedEligibleProducts}
|
|
1481
|
+
previewLoading={previewLoading}
|
|
1482
|
+
onPreview={handlePreview}
|
|
1483
|
+
filteredProducts={filteredProducts}
|
|
1484
|
+
selectedProductSet={selectedProductSet}
|
|
1485
|
+
onToggleProduct={handleProductCardPress}
|
|
1486
|
+
onMarkCardPressHandled={markNextProductCardPressAsHandled}
|
|
1487
|
+
/>
|
|
1488
|
+
)}
|
|
1489
|
+
|
|
1490
|
+
{activeTab === 'remoteCatalog' && (
|
|
1491
|
+
<RemoteMarketplaceMenuTab
|
|
1492
|
+
shadowStyle={integrationCardShadowStyle}
|
|
1493
|
+
accentColor={brandColors.primary}
|
|
1494
|
+
palette={brandColors}
|
|
1495
|
+
title="Cardapio no 99"
|
|
1496
|
+
subtitle="Leitura direta do cardapio remoto da loja selecionada na 99Food."
|
|
1497
|
+
buttonLabel="Ler cardapio do 99"
|
|
1498
|
+
loading={actionLoading === 'remote-menu'}
|
|
1499
|
+
snapshot={remoteMenuSnapshot}
|
|
1500
|
+
errorMessage={remoteMenuError}
|
|
1501
|
+
onLoad={handleLoadRemoteMenu}
|
|
1502
|
+
/>
|
|
1503
|
+
)}
|
|
1504
|
+
</ScrollView>
|
|
1505
|
+
|
|
1506
|
+
<Food99PreviewModal
|
|
1507
|
+
visible={previewVisible}
|
|
1508
|
+
previewData={previewData}
|
|
1509
|
+
selectedEligibleProducts={selectedEligibleProducts}
|
|
1510
|
+
uploading={uploading}
|
|
1511
|
+
accentColor={brandColors.primary}
|
|
1512
|
+
palette={brandColors}
|
|
1513
|
+
onClose={() => setPreviewVisible(false)}
|
|
1514
|
+
onUpload={handleUpload}
|
|
1515
|
+
/>
|
|
1516
|
+
|
|
1517
|
+
<Food99QuickWalletModal
|
|
1518
|
+
visible={quickWalletModalVisible}
|
|
1519
|
+
walletName={quickWalletName}
|
|
1520
|
+
setWalletName={setQuickWalletName}
|
|
1521
|
+
actionLoading={actionLoading}
|
|
1522
|
+
accentColor={brandColors.primary}
|
|
1523
|
+
palette={brandColors}
|
|
1524
|
+
onClose={closeQuickWalletModal}
|
|
1525
|
+
onCreate={handleCreateQuickWallet}
|
|
1526
|
+
/>
|
|
1527
|
+
</SafeAreaView>
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
// TODO(store-first): quando este arquivo for mexido, mover a leitura para stores, remover api.fetch e evitar repassar dados em objetos quando o store ja resolver isso.
|