@controleonline/ui-common 1.2.70 → 1.2.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +32 -28
- package/src/api/fetch.js +1 -1
- package/src/api/index.js +328 -58
- package/src/react/components/AddImportModal.js +95 -111
- package/src/react/components/AnimatedModal.js +171 -0
- package/src/react/components/AnimatedModal.styles.js +21 -0
- package/src/react/components/AppTypeSwitcher.js +164 -0
- package/src/react/components/AppTypeSwitcher.styles.js +109 -0
- package/src/react/components/BackgroundRuntimeBridge.js +5 -4
- package/src/react/components/BottomNavigationBar.config.js +211 -231
- package/src/react/components/BottomNavigationBar.js +86 -31
- package/src/react/components/BottomNavigationBar.styles.js +118 -110
- package/src/react/components/CategoryForm.js +52 -35
- package/src/react/components/DefaultProvider.native.js +194 -156
- package/src/react/components/DefaultProvider.web.js +188 -112
- package/src/react/components/DeliveryPushBridge.native.js +2 -2
- package/src/react/components/DeviceAlertSoundService.js +3 -3
- package/src/react/components/KioskModeBridge.js +2 -2
- package/src/react/components/LauncherModeBridge.js +2 -2
- package/src/react/components/ManagerPushBridge.native.js +2 -2
- package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
- package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
- package/src/react/components/RemoteCheckoutService.js +28 -20
- package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
- package/src/react/components/RuntimeInfoFooter.js +137 -45
- package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
- package/src/react/components/StateStore.js +258 -0
- package/src/react/components/UnifiedPaymentBar.js +7 -4
- package/src/react/components/UserAvatar.js +160 -0
- package/src/react/components/WebsocketListener.native.js +11 -11
- 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 +71 -30
- package/src/react/css/orders.js +72 -0
- 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/SettingsPage/PaymentTypesByWalletTab.js +484 -454
- package/src/react/pages/SettingsPage/index.js +1266 -1167
- 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/print/providers/local.js +1 -1
- package/src/react/router/publicRoutes.js +25 -0
- package/src/react/services/Cielo/Checkout.js +39 -0
- package/src/react/services/Cielo/Cielo.js +193 -0
- package/src/react/services/Cielo/Print.js +7 -0
- package/src/react/services/InfinitePay/Checkout.js +33 -0
- package/src/react/services/InfinitePay/InfinitePay.js +24 -0
- package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
- package/src/react/styles/global.js +115 -0
- package/src/react/utils/categoryContexts.js +46 -0
- package/src/react/utils/entityDisplay.js +222 -217
- package/src/react/utils/fileUrl.js +201 -16
- package/src/react/utils/integrationPage.js +16 -0
- package/src/react/utils/menuNavigation.js +31 -0
- package/src/react/utils/orderIdentity.js +277 -0
- package/src/react/utils/remotePayment.js +115 -61
- package/src/react/utils/runtimeFooter.js +40 -22
- package/src/react/utils/runtimeLanguage.js +50 -9
- package/src/react/utils/runtimeMenu.js +190 -98
- package/src/react/utils/runtimeZoom.js +43 -0
- package/src/react/utils/shopConfig.js +72 -34
- package/src/react/utils/shopFranchises.js +182 -23
- package/src/react/utils/socketRuntimePipeline.js +14 -14
- 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/configs/index.js +2 -2
- 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/api/loadSmokeIndex.test.js +75 -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/config/deviceConfigBootstrap.test.js +47 -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/print/localPrintProvider.test.js +1 -1
- package/src/tests/react/services/Cielo.test.js +190 -0
- 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 +62 -0
- package/src/tests/react/utils/formatter.test.mjs +33 -0
- package/src/tests/react/utils/importStatus.test.js +2 -2
- package/src/tests/react/utils/orderIdentity.test.js +139 -0
- package/src/tests/react/utils/remotePayment.test.js +62 -0
- package/src/tests/react/utils/runtimeFooter.test.js +32 -9
- package/src/tests/react/utils/runtimeLanguage.test.js +80 -10
- package/src/tests/react/utils/runtimeMenu.test.js +195 -111
- package/src/tests/react/utils/runtimeZoom.test.js +38 -0
- package/src/tests/react/utils/shopConfig.test.js +75 -0
- package/src/tests/react/utils/shopFranchises.test.js +122 -12
- 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 +252 -396
- package/src/tests/react/utils/userAvatar.test.js +63 -0
- package/src/utils/appDomain.js +15 -4
- package/src/utils/formatter.js +122 -16
- package/src/utils/integrationConfigs.js +1 -0
- package/src/utils/translate.js +277 -398
- 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,91 @@
|
|
|
1
|
+
// Filtros de catálogo exibidos na aba de produtos.
|
|
2
|
+
export const filterTabs = [
|
|
3
|
+
{ key: 'all', label: 'Todos' },
|
|
4
|
+
{ key: 'eligible', label: 'Elegiveis' },
|
|
5
|
+
{ key: 'selected', label: 'Selecionados' },
|
|
6
|
+
{ key: 'blocked', label: 'Com bloqueio' },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
// Ordem e rótulos fixos usados na edição dos horários de funcionamento.
|
|
10
|
+
export const DAY_LABELS = {
|
|
11
|
+
MONDAY: 'Segunda',
|
|
12
|
+
TUESDAY: 'Terça',
|
|
13
|
+
WEDNESDAY: 'Quarta',
|
|
14
|
+
THURSDAY: 'Quinta',
|
|
15
|
+
FRIDAY: 'Sexta',
|
|
16
|
+
SATURDAY: 'Sábado',
|
|
17
|
+
SUNDAY: 'Domingo',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const DAY_ORDER = ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'];
|
|
21
|
+
|
|
22
|
+
export const formatIFoodApiError = error => {
|
|
23
|
+
if (!error) return 'Nao foi possivel carregar os dados da integracao iFood.';
|
|
24
|
+
if (typeof error === 'string') return error;
|
|
25
|
+
return error?.message || error?.description || error?.errmsg || 'Nao foi possivel carregar os dados da integracao iFood.';
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const calcEndTime = (start, durationMin) => {
|
|
29
|
+
if (!start || durationMin == null || durationMin === '') return '--:--';
|
|
30
|
+
const [hours, minutes] = start.split(':').map(Number);
|
|
31
|
+
const totalMinutes = hours * 60 + minutes + Number(durationMin);
|
|
32
|
+
const endHours = Math.floor(totalMinutes / 60) % 24;
|
|
33
|
+
const endMinutes = totalMinutes % 60;
|
|
34
|
+
return `${String(endHours).padStart(2, '0')}:${String(endMinutes).padStart(2, '0')}`;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const calcDuration = (start, end) => {
|
|
38
|
+
const [startHours, startMinutes] = (start || '00:00').split(':').map(Number);
|
|
39
|
+
const [endHours, endMinutes] = (end || '23:59').split(':').map(Number);
|
|
40
|
+
let duration = endHours * 60 + endMinutes - (startHours * 60 + startMinutes);
|
|
41
|
+
if (duration <= 0) duration += 1440;
|
|
42
|
+
return duration;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const formatDateTimeLabel = value => {
|
|
46
|
+
if (!value) return '--';
|
|
47
|
+
const parsed = new Date(value);
|
|
48
|
+
if (Number.isNaN(parsed.getTime())) return String(value);
|
|
49
|
+
|
|
50
|
+
return parsed.toLocaleString('pt-BR', {
|
|
51
|
+
day: '2-digit',
|
|
52
|
+
month: '2-digit',
|
|
53
|
+
year: 'numeric',
|
|
54
|
+
hour: '2-digit',
|
|
55
|
+
minute: '2-digit',
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const pad = value => String(value).padStart(2, '0');
|
|
60
|
+
|
|
61
|
+
export const toDateTimeLocalValue = value => {
|
|
62
|
+
const parsed = value ? new Date(value) : new Date();
|
|
63
|
+
const date = Number.isNaN(parsed.getTime()) ? new Date() : parsed;
|
|
64
|
+
|
|
65
|
+
return [
|
|
66
|
+
date.getFullYear(),
|
|
67
|
+
pad(date.getMonth() + 1),
|
|
68
|
+
pad(date.getDate()),
|
|
69
|
+
].join('-') + `T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const buildDefaultInterruptionDraft = () => {
|
|
73
|
+
const start = new Date();
|
|
74
|
+
start.setMinutes(start.getMinutes() + 5);
|
|
75
|
+
const end = new Date(start.getTime() + (60 * 60 * 1000));
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
description: 'Pausa operacional',
|
|
79
|
+
start: toDateTimeLocalValue(start),
|
|
80
|
+
end: toDateTimeLocalValue(end),
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const toIFoodInterruptionDateTime = value => {
|
|
85
|
+
const normalized = String(value || '').trim();
|
|
86
|
+
if (!normalized) return '';
|
|
87
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(normalized)) {
|
|
88
|
+
return `${normalized}:00`;
|
|
89
|
+
}
|
|
90
|
+
return normalized;
|
|
91
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './IFoodIntegrationPage/index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './IFoodIntegrationPage/styles';
|
|
@@ -28,12 +28,31 @@ const formatDate = (dateString) => {
|
|
|
28
28
|
const d = new Date(dateString);
|
|
29
29
|
return d.toLocaleString(); // Formato local legível
|
|
30
30
|
};
|
|
31
|
-
|
|
32
|
-
const Imports = ({ context = {}, onClose }) => {
|
|
33
|
-
const peopleStore = useStore('people');
|
|
34
|
-
const
|
|
35
|
-
const {
|
|
36
|
-
|
|
31
|
+
|
|
32
|
+
const Imports = ({ context = {}, onClose }) => {
|
|
33
|
+
const peopleStore = useStore('people');
|
|
34
|
+
const themeStore = useStore('theme');
|
|
35
|
+
const { getters: peopleGetters } = peopleStore.getters;
|
|
36
|
+
const { currentCompany } = peopleGetters;
|
|
37
|
+
const themeColors = themeStore?.getters?.colors || {};
|
|
38
|
+
const buttonPalette = useMemo(
|
|
39
|
+
() => ({
|
|
40
|
+
buttonBackground: themeColors.buttonBackground,
|
|
41
|
+
buttonBorder: themeColors.buttonBorder,
|
|
42
|
+
buttonText: themeColors.buttonText,
|
|
43
|
+
buttonIcon: themeColors.buttonIcon || themeColors.buttonText,
|
|
44
|
+
modalCloseIcon: themeColors.modalCloseIcon || themeColors.iconColor,
|
|
45
|
+
}),
|
|
46
|
+
[
|
|
47
|
+
themeColors.buttonBackground,
|
|
48
|
+
themeColors.buttonBorder,
|
|
49
|
+
themeColors.buttonIcon,
|
|
50
|
+
themeColors.buttonText,
|
|
51
|
+
themeColors.iconColor,
|
|
52
|
+
themeColors.modalCloseIcon,
|
|
53
|
+
],
|
|
54
|
+
);
|
|
55
|
+
|
|
37
56
|
const importType = context.context;
|
|
38
57
|
const title = context.title;
|
|
39
58
|
const searchPlaceholder = context.searchPlaceholder;
|
|
@@ -45,25 +64,25 @@ const Imports = ({ context = {}, onClose }) => {
|
|
|
45
64
|
noStatusLabel,
|
|
46
65
|
processingHelpLabel,
|
|
47
66
|
} = resolveImportLabels(global.t?.t);
|
|
48
|
-
|
|
49
|
-
const navigation = useNavigation();
|
|
50
|
-
|
|
51
|
-
const importsStore = useStore('imports');
|
|
52
|
-
const getters = importsStore.getters;
|
|
53
|
-
const actions = importsStore.actions;
|
|
54
|
-
|
|
67
|
+
|
|
68
|
+
const navigation = useNavigation();
|
|
69
|
+
|
|
70
|
+
const importsStore = useStore('imports');
|
|
71
|
+
const getters = importsStore.getters;
|
|
72
|
+
const actions = importsStore.actions;
|
|
73
|
+
|
|
55
74
|
const { items, isLoading } = getters;
|
|
56
75
|
|
|
57
76
|
const [currentPage, setCurrentPage] = useState(1);
|
|
58
77
|
|
|
59
78
|
const [searchText, setSearchText] = useState('');
|
|
60
79
|
const [searchQuery, setSearchQuery] = useState('');
|
|
61
|
-
|
|
62
|
-
const [allImports, setAllImports] = useState([]);
|
|
63
|
-
|
|
64
|
-
const [refreshing, setRefreshing] = useState(false);
|
|
65
|
-
const [showAddImportModal, setShowAddImportModal] = useState(false);
|
|
66
|
-
|
|
80
|
+
|
|
81
|
+
const [allImports, setAllImports] = useState([]);
|
|
82
|
+
|
|
83
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
84
|
+
const [showAddImportModal, setShowAddImportModal] = useState(false);
|
|
85
|
+
|
|
67
86
|
const fetchImports = useCallback(
|
|
68
87
|
(query, page) => {
|
|
69
88
|
const params = {
|
|
@@ -71,43 +90,43 @@ const Imports = ({ context = {}, onClose }) => {
|
|
|
71
90
|
importType: importType,
|
|
72
91
|
page: page ?? currentPage,
|
|
73
92
|
};
|
|
74
|
-
|
|
75
|
-
if (String(query ?? searchQuery).trim()) {
|
|
76
|
-
params.name = String(query ?? searchQuery).trim();
|
|
77
|
-
}
|
|
78
|
-
|
|
93
|
+
|
|
94
|
+
if (String(query ?? searchQuery).trim()) {
|
|
95
|
+
params.name = String(query ?? searchQuery).trim();
|
|
96
|
+
}
|
|
97
|
+
|
|
79
98
|
return actions.getItems(params);
|
|
80
99
|
},
|
|
81
100
|
[actions, currentCompany, currentPage, searchQuery, importType],
|
|
82
101
|
);
|
|
83
|
-
|
|
84
|
-
useLayoutEffect(() => {
|
|
85
|
-
navigation.setOptions({
|
|
86
|
-
headerTitle: title,
|
|
87
|
-
});
|
|
88
|
-
}, [navigation, title]);
|
|
89
|
-
|
|
102
|
+
|
|
103
|
+
useLayoutEffect(() => {
|
|
104
|
+
navigation.setOptions({
|
|
105
|
+
headerTitle: title,
|
|
106
|
+
});
|
|
107
|
+
}, [navigation, title]);
|
|
108
|
+
|
|
90
109
|
useFocusEffect(
|
|
91
110
|
useCallback(() => {
|
|
92
111
|
fetchImports(searchQuery, currentPage);
|
|
93
112
|
}, [currentPage, fetchImports, searchQuery]),
|
|
94
113
|
);
|
|
95
|
-
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
if (isLoading) return;
|
|
98
|
-
if (items && Array.isArray(items)) {
|
|
99
|
-
if (currentPage === 1) {
|
|
100
|
-
setAllImports(items);
|
|
101
|
-
} else {
|
|
102
|
-
setAllImports((prev) => {
|
|
103
|
-
const newIds = new Set(items.map((i) => i.id));
|
|
104
|
-
const filteredPrev = prev.filter((p) => !newIds.has(p.id));
|
|
105
|
-
return [...filteredPrev, ...items];
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}, [items, currentPage, isLoading]);
|
|
110
|
-
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (isLoading) return;
|
|
117
|
+
if (items && Array.isArray(items)) {
|
|
118
|
+
if (currentPage === 1) {
|
|
119
|
+
setAllImports(items);
|
|
120
|
+
} else {
|
|
121
|
+
setAllImports((prev) => {
|
|
122
|
+
const newIds = new Set(items.map((i) => i.id));
|
|
123
|
+
const filteredPrev = prev.filter((p) => !newIds.has(p.id));
|
|
124
|
+
return [...filteredPrev, ...items];
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}, [items, currentPage, isLoading]);
|
|
129
|
+
|
|
111
130
|
useEffect(() => {
|
|
112
131
|
const t = setTimeout(() => {
|
|
113
132
|
setSearchQuery(searchText.trim());
|
|
@@ -167,21 +186,21 @@ const Imports = ({ context = {}, onClose }) => {
|
|
|
167
186
|
<Text style={styles.avatarText}>
|
|
168
187
|
{item.name?.charAt(0)?.toUpperCase() || 'I'}
|
|
169
188
|
</Text>
|
|
170
|
-
</View>
|
|
171
|
-
|
|
172
|
-
<Text style={styles.title} numberOfLines={1}>
|
|
173
|
-
{item.name} (ID: {item.id})
|
|
174
|
-
</Text>
|
|
175
|
-
|
|
176
|
-
<Icon name="chevron-right" size={14} color="#CBD5E1" />
|
|
177
|
-
</View>
|
|
178
|
-
|
|
179
|
-
<View style={styles.cardBody}>
|
|
180
|
-
<View style={styles.infoRow}>
|
|
181
|
-
<Icon name="file-text-o" size={16} color={colors.primary} />
|
|
182
|
-
<Text style={styles.infoText}>{item.fileFormat?.toUpperCase()}</Text>
|
|
183
|
-
</View>
|
|
184
|
-
|
|
189
|
+
</View>
|
|
190
|
+
|
|
191
|
+
<Text style={styles.title} numberOfLines={1}>
|
|
192
|
+
{item.name} (ID: {item.id})
|
|
193
|
+
</Text>
|
|
194
|
+
|
|
195
|
+
<Icon name="chevron-right" size={14} color="#CBD5E1" />
|
|
196
|
+
</View>
|
|
197
|
+
|
|
198
|
+
<View style={styles.cardBody}>
|
|
199
|
+
<View style={styles.infoRow}>
|
|
200
|
+
<Icon name="file-text-o" size={16} color={colors.primary} />
|
|
201
|
+
<Text style={styles.infoText}>{item.fileFormat?.toUpperCase()}</Text>
|
|
202
|
+
</View>
|
|
203
|
+
|
|
185
204
|
<View style={styles.infoRow}>
|
|
186
205
|
<Icon name="calendar" size={16} color={colors.primary} />
|
|
187
206
|
<Text style={styles.infoText}>{formatDate(item.uploadDate)}</Text>
|
|
@@ -226,74 +245,96 @@ const Imports = ({ context = {}, onClose }) => {
|
|
|
226
245
|
</TouchableOpacity>
|
|
227
246
|
);
|
|
228
247
|
};
|
|
229
|
-
|
|
230
|
-
const downloadTemplate = async () => {
|
|
231
|
-
try {
|
|
232
|
-
const response = await api.fetch(`/imports/example/${importType}`, {
|
|
233
|
-
method: 'GET',
|
|
234
|
-
responseType: 'text',
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const csvText = typeof response === 'string' ? response : await response.text?.();
|
|
238
|
-
console.log('CSV recebido:', csvText.slice(0, 200));
|
|
239
|
-
|
|
240
|
-
if (!csvText) throw new Error('CSV vazio');
|
|
241
|
-
|
|
242
|
-
// mobile: verifica se o diretório está disponível
|
|
243
|
-
const dirUri = Directory?.cacheDocumentDirectory || Directory?.documentDirectory;
|
|
244
|
-
|
|
245
|
-
if (dirUri) {
|
|
246
|
-
const fileUri = `${dirUri}modelo_${importType}.csv`;
|
|
247
|
-
|
|
248
|
-
const file = new File(fileUri, { contents: csvText });
|
|
249
|
-
await file.create();
|
|
250
|
-
|
|
251
|
-
if (await Sharing.isAvailableAsync()) {
|
|
252
|
-
await Sharing.shareAsync(fileUri);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
console.log(`Arquivo CSV criado em: ${fileUri}`);
|
|
256
|
-
return fileUri;
|
|
257
|
-
} else if (typeof window !== 'undefined') {
|
|
258
|
-
// fallback web
|
|
259
|
-
const blob = new Blob([csvText], { type: 'text/csv' });
|
|
260
|
-
const link = document.createElement('a');
|
|
261
|
-
link.href = URL.createObjectURL(blob);
|
|
262
|
-
link.download = `modelo_${importType}.csv`;
|
|
263
|
-
link.click();
|
|
264
|
-
URL.revokeObjectURL(link.href);
|
|
265
|
-
console.log('Download CSV disparado no web');
|
|
266
|
-
return 'web-download';
|
|
267
|
-
} else {
|
|
268
|
-
throw new Error('FileSystem não disponível');
|
|
269
|
-
}
|
|
270
|
-
} catch (err) {
|
|
271
|
-
console.error('Erro detalhado ao baixar CSV:', err);
|
|
272
|
-
throw new Error('Falha ao baixar o modelo CSV');
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
return (
|
|
277
|
-
<View style={styles.container}>
|
|
278
|
-
|
|
248
|
+
|
|
249
|
+
const downloadTemplate = async () => {
|
|
250
|
+
try {
|
|
251
|
+
const response = await api.fetch(`/imports/example/${importType}`, {
|
|
252
|
+
method: 'GET',
|
|
253
|
+
responseType: 'text',
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const csvText = typeof response === 'string' ? response : await response.text?.();
|
|
257
|
+
console.log('CSV recebido:', csvText.slice(0, 200));
|
|
258
|
+
|
|
259
|
+
if (!csvText) throw new Error('CSV vazio');
|
|
260
|
+
|
|
261
|
+
// mobile: verifica se o diretório está disponível
|
|
262
|
+
const dirUri = Directory?.cacheDocumentDirectory || Directory?.documentDirectory;
|
|
263
|
+
|
|
264
|
+
if (dirUri) {
|
|
265
|
+
const fileUri = `${dirUri}modelo_${importType}.csv`;
|
|
266
|
+
|
|
267
|
+
const file = new File(fileUri, { contents: csvText });
|
|
268
|
+
await file.create();
|
|
269
|
+
|
|
270
|
+
if (await Sharing.isAvailableAsync()) {
|
|
271
|
+
await Sharing.shareAsync(fileUri);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log(`Arquivo CSV criado em: ${fileUri}`);
|
|
275
|
+
return fileUri;
|
|
276
|
+
} else if (typeof window !== 'undefined') {
|
|
277
|
+
// fallback web
|
|
278
|
+
const blob = new Blob([csvText], { type: 'text/csv' });
|
|
279
|
+
const link = document.createElement('a');
|
|
280
|
+
link.href = URL.createObjectURL(blob);
|
|
281
|
+
link.download = `modelo_${importType}.csv`;
|
|
282
|
+
link.click();
|
|
283
|
+
URL.revokeObjectURL(link.href);
|
|
284
|
+
console.log('Download CSV disparado no web');
|
|
285
|
+
return 'web-download';
|
|
286
|
+
} else {
|
|
287
|
+
throw new Error('FileSystem não disponível');
|
|
288
|
+
}
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.error('Erro detalhado ao baixar CSV:', err);
|
|
291
|
+
throw new Error('Falha ao baixar o modelo CSV');
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<View style={styles.container}>
|
|
297
|
+
|
|
279
298
|
<View style={styles.headerBar}>
|
|
280
299
|
<Text style={styles.headerTitle}>{title}</Text>
|
|
281
300
|
<TouchableOpacity onPress={onClose}>
|
|
282
|
-
<Icon name="close" size={24} color=
|
|
301
|
+
<Icon name="close" size={24} color={buttonPalette.modalCloseIcon} />
|
|
283
302
|
</TouchableOpacity>
|
|
284
|
-
</View>
|
|
285
|
-
|
|
286
|
-
<View style={styles.subHeader}>
|
|
303
|
+
</View>
|
|
304
|
+
|
|
305
|
+
<View style={styles.subHeader}>
|
|
287
306
|
<View style={styles.topRow}>
|
|
288
307
|
<View style={styles.topRowActions}>
|
|
289
308
|
<TouchableOpacity style={styles.downloadButton} onPress={downloadTemplate}>
|
|
290
|
-
<
|
|
291
|
-
|
|
309
|
+
<View
|
|
310
|
+
style={{
|
|
311
|
+
flexDirection: 'row',
|
|
312
|
+
alignItems: 'center',
|
|
313
|
+
backgroundColor: buttonPalette.buttonBackground,
|
|
314
|
+
borderColor: buttonPalette.buttonBorder,
|
|
315
|
+
borderWidth: 1,
|
|
316
|
+
borderRadius: 8,
|
|
317
|
+
paddingHorizontal: 12,
|
|
318
|
+
paddingVertical: 6,
|
|
319
|
+
}}
|
|
320
|
+
>
|
|
321
|
+
<IconAdd name="download" size={20} color={buttonPalette.buttonIcon} />
|
|
322
|
+
<Text style={[styles.downloadText, { color: buttonPalette.buttonText }]}>Baixar Modelo</Text>
|
|
323
|
+
</View>
|
|
292
324
|
</TouchableOpacity>
|
|
293
325
|
|
|
294
|
-
<TouchableOpacity
|
|
295
|
-
|
|
296
|
-
|
|
326
|
+
<TouchableOpacity
|
|
327
|
+
style={[
|
|
328
|
+
styles.refreshButton,
|
|
329
|
+
{
|
|
330
|
+
backgroundColor: buttonPalette.buttonBackground,
|
|
331
|
+
borderColor: buttonPalette.buttonBorder,
|
|
332
|
+
},
|
|
333
|
+
]}
|
|
334
|
+
onPress={onRefresh}
|
|
335
|
+
>
|
|
336
|
+
<Icon name="refresh" size={16} color={buttonPalette.buttonIcon} />
|
|
337
|
+
<Text style={[styles.refreshButtonText, { color: buttonPalette.buttonText }]}>{refreshLabel}</Text>
|
|
297
338
|
</TouchableOpacity>
|
|
298
339
|
</View>
|
|
299
340
|
|
|
@@ -304,50 +345,57 @@ const Imports = ({ context = {}, onClose }) => {
|
|
|
304
345
|
</View>
|
|
305
346
|
) : null}
|
|
306
347
|
</View>
|
|
307
|
-
|
|
308
|
-
<View style={styles.searchRow}>
|
|
309
|
-
<View style={styles.searchInputContainer}>
|
|
310
|
-
<Icon name="search" size={16} color="#94A3B8" />
|
|
311
|
-
|
|
312
|
-
<TextInput
|
|
313
|
-
style={styles.searchInput}
|
|
314
|
-
placeholder={searchPlaceholder}
|
|
315
|
-
placeholderTextColor="#94A3B8"
|
|
316
|
-
value={searchText}
|
|
317
|
-
onChangeText={setSearchText}
|
|
318
|
-
/>
|
|
319
|
-
|
|
320
|
-
{searchText.length > 0 && (
|
|
321
|
-
<TouchableOpacity
|
|
322
|
-
onPress={() => setSearchText('')}
|
|
323
|
-
style={styles.clearSearchButton}
|
|
324
|
-
>
|
|
325
|
-
<Icon name="times-circle" size={16} color="#94A3B8" />
|
|
326
|
-
</TouchableOpacity>
|
|
327
|
-
)}
|
|
328
|
-
</View>
|
|
329
|
-
|
|
330
|
-
<TouchableOpacity
|
|
331
|
-
style={
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
348
|
+
|
|
349
|
+
<View style={styles.searchRow}>
|
|
350
|
+
<View style={styles.searchInputContainer}>
|
|
351
|
+
<Icon name="search" size={16} color="#94A3B8" />
|
|
352
|
+
|
|
353
|
+
<TextInput
|
|
354
|
+
style={styles.searchInput}
|
|
355
|
+
placeholder={searchPlaceholder}
|
|
356
|
+
placeholderTextColor="#94A3B8"
|
|
357
|
+
value={searchText}
|
|
358
|
+
onChangeText={setSearchText}
|
|
359
|
+
/>
|
|
360
|
+
|
|
361
|
+
{searchText.length > 0 && (
|
|
362
|
+
<TouchableOpacity
|
|
363
|
+
onPress={() => setSearchText('')}
|
|
364
|
+
style={styles.clearSearchButton}
|
|
365
|
+
>
|
|
366
|
+
<Icon name="times-circle" size={16} color="#94A3B8" />
|
|
367
|
+
</TouchableOpacity>
|
|
368
|
+
)}
|
|
369
|
+
</View>
|
|
370
|
+
|
|
371
|
+
<TouchableOpacity
|
|
372
|
+
style={[
|
|
373
|
+
styles.addButton,
|
|
374
|
+
{
|
|
375
|
+
backgroundColor: buttonPalette.buttonBackground,
|
|
376
|
+
borderColor: buttonPalette.buttonBorder,
|
|
377
|
+
borderWidth: 1,
|
|
378
|
+
},
|
|
379
|
+
]}
|
|
380
|
+
onPress={() => setShowAddImportModal(true)}
|
|
381
|
+
>
|
|
382
|
+
<IconAdd name="upload-file" size={24} color={buttonPalette.buttonIcon} />
|
|
383
|
+
</TouchableOpacity>
|
|
384
|
+
</View>
|
|
385
|
+
</View>
|
|
386
|
+
|
|
387
|
+
<FlatList
|
|
388
|
+
data={allImports}
|
|
389
|
+
keyExtractor={(item) => String(item.id)}
|
|
390
|
+
renderItem={renderImportCard}
|
|
391
|
+
contentContainerStyle={styles.scrollContent}
|
|
392
|
+
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
|
393
|
+
/>
|
|
394
|
+
|
|
395
|
+
<AddImportModal
|
|
396
|
+
visible={showAddImportModal}
|
|
397
|
+
onClose={() => setShowAddImportModal(false)}
|
|
398
|
+
context={context}
|
|
351
399
|
onSuccess={() => {
|
|
352
400
|
setCurrentPage(1);
|
|
353
401
|
fetchImports(searchQuery, 1);
|
|
@@ -356,6 +404,6 @@ const Imports = ({ context = {}, onClose }) => {
|
|
|
356
404
|
</View>
|
|
357
405
|
);
|
|
358
406
|
};
|
|
359
|
-
|
|
407
|
+
|
|
360
408
|
export default Imports;
|
|
361
409
|
// 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.
|