diff --git a/.eslint-baseline.json b/.eslint-baseline.json index 2a416ba48..e1ab6b280 100644 --- a/.eslint-baseline.json +++ b/.eslint-baseline.json @@ -1,6 +1,6 @@ { - "generatedAt": "2026-05-23T00:33:24.495Z", - "totalErrors": 442, + "generatedAt": "2026-05-24T01:05:09.416Z", + "totalErrors": 177, "counts": { "src/components/access/DevAccessDeniedPage.tsx": { "react-hooks/exhaustive-deps": 1 @@ -8,18 +8,9 @@ "src/components/admin/OwnershipRepairDialog.tsx": { "@typescript-eslint/naming-convention": 1 }, - "src/components/admin/connections/ConnectionTestHistoryPanel.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/components/admin/connections/ConnectionsOverviewFilters.tsx": { "@typescript-eslint/naming-convention": 1 }, - "src/components/admin/connections/CredentialsSourceIndicator.tsx": { - "@typescript-eslint/no-non-null-assertion": 2 - }, - "src/components/admin/connections/FailedDeliveriesPanel.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/components/admin/connections/IncidentDetailsDrawer.tsx": { "@typescript-eslint/naming-convention": 1 }, @@ -29,29 +20,12 @@ "src/components/admin/connections/JustSavedFlash.tsx": { "@typescript-eslint/naming-convention": 3 }, - "src/components/admin/connections/SecretsManagerHealthPanel.tsx": { - "@typescript-eslint/no-non-null-assertion": 2 - }, - "src/components/admin/connections/SupabaseConnectionsTab.tsx": { - "@typescript-eslint/no-non-null-assertion": 17 - }, "src/components/admin/connections/ZoneSection.tsx": { "@typescript-eslint/naming-convention": 1 }, - "src/components/admin/connections/__tests__/ConnectionUI.test.tsx": { - "@typescript-eslint/no-explicit-any": 3, - "no-duplicate-imports": 1 - }, - "src/components/admin/connections/__tests__/ConnectionsOverviewTable.test.tsx": { - "@typescript-eslint/no-explicit-any": 7, - "no-duplicate-imports": 1 - }, "src/components/admin/connections/useFocusContext.ts": { "@typescript-eslint/naming-convention": 1 }, - "src/components/admin/connections/useSecretField.ts": { - "react-hooks/exhaustive-deps": 1 - }, "src/components/admin/personalization-manager/ProductPersonalizationManager.tsx": { "@typescript-eslint/no-non-null-assertion": 1 }, @@ -97,34 +71,18 @@ "src/components/admin/products/new-supplier/useNewSupplierForm.ts": { "@typescript-eslint/no-non-null-assertion": 1 }, - "src/components/admin/products/sections/ProductClassificationSection.tsx": { - "@typescript-eslint/naming-convention": 1, - "@typescript-eslint/no-non-null-assertion": 5 - }, "src/components/admin/products/useProductsManager.ts": { "react-hooks/exhaustive-deps": 5 }, "src/components/admin/security/HardeningHealthCard.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/components/admin/security/keys/UpdateMcpKeyDialog.tsx": { - "@typescript-eslint/no-non-null-assertion": 3 - }, "src/components/admin/security/role-migration/RoleMigrationPanel.tsx": { "@typescript-eslint/naming-convention": 1 }, "src/components/admin/suppliers-manager/useSuppliersManager.ts": { "@typescript-eslint/no-non-null-assertion": 3 }, - "src/components/admin/telemetry/AppHealthDashboard.tsx": { - "@typescript-eslint/no-non-null-assertion": 3 - }, - "src/components/admin/telemetry/BridgeCallDetailDrawer.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, - "src/components/admin/telemetry/RegressionGuardrailBanner.tsx": { - "@typescript-eslint/no-non-null-assertion": 5 - }, "src/components/admin/users/RoleAuditLogPanel.tsx": { "react-hooks/exhaustive-deps": 2 }, @@ -132,45 +90,23 @@ "@typescript-eslint/no-unused-vars": 2 }, "src/components/auth/ForgotPasswordForm.tsx": { - "@typescript-eslint/no-unused-vars": 3 + "@typescript-eslint/no-unused-vars": 2 }, "src/components/auth/KnownDevicesManager.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/components/auth/PasswordStrengthIndicator.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/bi/BIAiCopilot.tsx": { "@typescript-eslint/no-unused-vars": 1 }, "src/components/bi/ClientAffinityProducts.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/bi/ClientComparator.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/bi/ClientSeasonalityHeatmap.tsx": { "@typescript-eslint/no-non-null-assertion": 1 }, - "src/components/bi/ExecutiveSummaryButton.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/cart/BundleSuggestionCard.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, - "src/components/catalog/BulkVariantWizard.tsx": { - "@typescript-eslint/no-explicit-any": 1, - "@typescript-eslint/no-non-null-assertion": 1 - }, - "src/components/catalog/CatalogContent.tsx": { - "@typescript-eslint/no-unused-vars": 16 - }, "src/components/catalog/CatalogHeader.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/categories/CategorySidebarPanel.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, "src/components/collections/CollectionDetailHeader.tsx": { "@typescript-eslint/no-unused-vars": 2 }, @@ -187,10 +123,7 @@ "@typescript-eslint/no-unused-vars": 1 }, "src/components/compare/ComparisonHighlights.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, - "src/components/compare/ComparisonScoreCard.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 + "@typescript-eslint/no-unused-vars": 1 }, "src/components/compare/ExportComparisonButton.tsx": { "@typescript-eslint/no-unused-vars": 1 @@ -198,25 +131,10 @@ "src/components/compare/FloatingCompareBar.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/compare/OtherSuppliersRow.tsx": { - "@typescript-eslint/no-explicit-any": 1 - }, "src/components/compare/SupplierComparisonModal.tsx": { "@typescript-eslint/no-unused-vars": 2 }, - "src/components/dashboard/MyClientsWidget.tsx": { - "@typescript-eslint/no-non-null-assertion": 2 - }, - "src/components/dashboard/MyDiscountRequestsWidget.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, - "src/components/dashboard/MyRecentQuotesWidget.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/components/dashboard/QuickActionsPanel.tsx": { - "@typescript-eslint/no-unused-vars": 7 - }, - "src/components/dashboard/RecentKitsWidget.tsx": { "@typescript-eslint/no-unused-vars": 1 }, "src/components/dev/DiagnosticProfiler.tsx": { @@ -231,33 +149,21 @@ "src/components/engraving/PricingPanel.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/expert/ExpertChatDialog.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/expert/FlowFilterPanel.tsx": { "@typescript-eslint/no-unused-expressions": 1 }, "src/components/expert/FlowFilterPrimitives.tsx": { "@typescript-eslint/naming-convention": 2 }, - "src/components/expert/ProductLinkRenderer.tsx": { - "@typescript-eslint/no-non-null-assertion": 3 - }, "src/components/expert/chat/useExpertChat.ts": { "@typescript-eslint/no-unused-expressions": 2, "react-hooks/exhaustive-deps": 3 }, - "src/components/filters/ColorGroupFilter.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, "src/components/filters/CommemorativeDateFilter.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, - "src/components/filters/ExternalCategoryFilter.tsx": { "@typescript-eslint/no-unused-vars": 1 }, "src/components/filters/FilterPanel.tsx": { - "@typescript-eslint/no-unused-vars": 6 + "@typescript-eslint/no-unused-vars": 5 }, "src/components/filters/InlineColorGroupFilter.tsx": { "@typescript-eslint/no-unused-expressions": 1 @@ -265,88 +171,35 @@ "src/components/filters/filter-panel/FilterSection.tsx": { "@typescript-eslint/naming-convention": 1 }, - "src/components/filters/filter-panel/sections/SizeFilter.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, "src/components/filters/filter-panel/useFilterPanelState.ts": { "react-hooks/exhaustive-deps": 3 }, "src/components/intelligence/CategoryRanking.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/components/intelligence/IntelligenceFilterBar.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/intelligence/MarketIntelligenceChart.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/intelligence/ProductRankingSearch.tsx": { - "@typescript-eslint/no-non-null-assertion": 2 - }, - "src/components/intelligence/RankingFilterToolbar.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, "src/components/intelligence/RealtimeBadge.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/intelligence/SalesOverviewChart.tsx": { - "@typescript-eslint/no-non-null-assertion": 1, - "@typescript-eslint/no-unused-vars": 2 - }, - "src/components/intelligence/SegmentAnalysis.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/intelligence/SupplierSales.tsx": { - "@typescript-eslint/no-non-null-assertion": 3 - }, - "src/components/intelligence/TrendingProducts.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/components/inventory/FutureStockDialog.tsx": { "@typescript-eslint/naming-convention": 1, "@typescript-eslint/no-non-null-assertion": 1 }, - "src/components/inventory/StockAlertDialogs.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, - "src/components/inventory/StockCategoryTreeSelect.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/inventory/StockDashboard.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/inventory/StockFilterToolbar.tsx": { - "@typescript-eslint/no-unused-vars": 4, + "@typescript-eslint/no-unused-vars": 3, "react-hooks/exhaustive-deps": 2 }, - "src/components/inventory/risk/ProductRiskDetail.tsx": { - "@typescript-eslint/no-non-null-assertion": 1, - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/inventory/risk/RiskKpi.tsx": { "@typescript-eslint/naming-convention": 1 }, - "src/components/kit-builder/BoxSelector.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/kit-builder/DiscontinuedItemsAlert.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, "src/components/kit-builder/KitBuilderHeader.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/kit-builder/KitIsometricPreview.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/kit-builder/KitSummary.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/kit-builder/SelectedItemsBadges.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/kit-builder/SimilarKitsWidget.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/kit-builder/VariantSelector.tsx": { "@typescript-eslint/no-non-null-assertion": 1, "@typescript-eslint/no-unused-vars": 1 @@ -354,14 +207,11 @@ "src/components/kit-builder/kit-summary/KitCompositionCard.tsx": { "@typescript-eslint/no-non-null-assertion": 2 }, - "src/components/layout/GlobalOverlay.tsx": { - "@typescript-eslint/no-unused-vars": 4 - }, "src/components/layout/MainLayout.tsx": { "react-hooks/exhaustive-deps": 1 }, "src/components/layout/SidebarReorganized.tsx": { - "@typescript-eslint/no-unused-vars": 5 + "@typescript-eslint/no-unused-vars": 4 }, "src/components/layout/sidebar/SidebarNavGroup.tsx": { "react-hooks/exhaustive-deps": 1 @@ -380,33 +230,15 @@ "@typescript-eslint/no-non-null-assertion": 1, "@typescript-eslint/no-unused-vars": 3 }, - "src/components/mobile/MobileProductActions.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, - "src/components/mobile/SmartMobileNav.tsx": { - "@typescript-eslint/no-unused-vars": 8 - }, "src/components/mockup/KeyboardShortcuts.tsx": { "react-hooks/exhaustive-deps": 1 }, "src/components/mockup/LogoColorAnalyzer.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, - "src/components/mockup/MockupConfigPanel.tsx": { - "@typescript-eslint/no-unused-vars": 2 + "@typescript-eslint/no-unused-vars": 1 }, "src/components/mockup/MockupHistoryPanel.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/components/mockup/MockupProductSelector.tsx": { - "@typescript-eslint/no-unused-vars": 3 - }, - "src/components/mockup/MockupResultCard.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/mockup/MockupWizard.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/mockup/ProductSearchCombobox.tsx": { "@typescript-eslint/no-non-null-assertion": 2 }, @@ -420,20 +252,11 @@ "src/components/mockup/logo-editor/LogoPreviewCanvas.tsx": { "@typescript-eslint/no-non-null-assertion": 1 }, - "src/components/navigation/DynamicBreadcrumbs.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/notifications/NotificationDrawer.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/notifications/NotificationsBadgeStatsPanel.tsx": { "react-hooks/exhaustive-deps": 1 }, "src/components/notifications/badge-stats/EfficiencyGrid.tsx": { - "@typescript-eslint/no-unused-vars": 4 - }, - "src/components/notifications/badge-stats/useNotificationsMetricsPanel.ts": { - "@typescript-eslint/no-unused-vars": 2 + "@typescript-eslint/no-unused-vars": 3 }, "src/components/novelties/NoveltyProductGrid.tsx": { "@typescript-eslint/no-non-null-assertion": 1, @@ -442,15 +265,8 @@ "src/components/novelties/NoveltyStatsCards.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/onboarding/OnboardingTour.tsx": { - "@typescript-eslint/no-unused-vars": 5 - }, - "src/components/pdf/PropostaComercialTailwind.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/pdf/proposal/LogoWithTransparentBg.tsx": { - "@typescript-eslint/no-non-null-assertion": 3, - "@typescript-eslint/no-unused-vars": 1 + "@typescript-eslint/no-non-null-assertion": 3 }, "src/components/pdf/proposal/ProposalFooter.tsx": { "@typescript-eslint/no-unused-vars": 2 @@ -461,47 +277,31 @@ "src/components/presentation/PresentationMode.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/components/pricing/ProductPriceSimulator.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/pricing/QuantityPriceCalculator.tsx": { "@typescript-eslint/no-unused-vars": 3 }, "src/components/pricing/simulator/CustomizationOptions.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/pricing/simulator/EngravingList.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/pricing/simulator/MultiEngravingResult.tsx": { "@typescript-eslint/no-non-null-assertion": 2, - "@typescript-eslint/no-unused-vars": 2 - }, - "src/components/pricing/simulator/PriceResultV51.tsx": { - "@typescript-eslint/no-unused-vars": 2 + "@typescript-eslint/no-unused-vars": 1 }, "src/components/pricing/simulator/ProductVariantSelector.tsx": { "@typescript-eslint/no-non-null-assertion": 1 }, "src/components/pricing/simulator/QuantityAndResult.tsx": { - "@typescript-eslint/no-unused-vars": 5 + "@typescript-eslint/no-unused-vars": 2 }, "src/components/pricing/simulator/TechniqueSelector.tsx": { "@typescript-eslint/no-non-null-assertion": 3, "react-hooks/exhaustive-deps": 1 }, "src/components/products/BulkActionBar.tsx": { - "@typescript-eslint/naming-convention": 1, - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/products/FutureStockModal.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/products/PackagingModal.tsx": { - "@typescript-eslint/no-unused-vars": 1 + "@typescript-eslint/naming-convention": 1 }, "src/components/products/ProductCard.tsx": { - "@typescript-eslint/no-unused-vars": 4 + "@typescript-eslint/no-unused-vars": 2 }, "src/components/products/ProductCardActions.tsx": { "@typescript-eslint/naming-convention": 1 @@ -519,16 +319,14 @@ "react-hooks/exhaustive-deps": 1 }, "src/components/products/ProductGallery.tsx": { - "@typescript-eslint/no-unused-expressions": 1, - "@typescript-eslint/no-unused-vars": 1 + "@typescript-eslint/no-unused-expressions": 1 }, "src/components/products/ProductGrid.test.tsx": { - "@typescript-eslint/no-explicit-any": 7, - "@typescript-eslint/no-unused-vars": 1 + "@typescript-eslint/no-explicit-any": 7 }, "src/components/products/ProductGrid.tsx": { "@typescript-eslint/no-explicit-any": 2, - "@typescript-eslint/no-unused-vars": 2 + "@typescript-eslint/no-unused-vars": 1 }, "src/components/products/ProductInfoBar.tsx": { "@typescript-eslint/no-unused-vars": 1 @@ -541,9 +339,6 @@ "@typescript-eslint/no-explicit-any": 2, "@typescript-eslint/no-non-null-assertion": 1 }, - "src/components/products/ProductListItem.tsx": { - "@typescript-eslint/no-unused-vars": 11 - }, "src/components/products/ProductPersonalizationRules.tsx": { "@typescript-eslint/no-unused-vars": 1 }, @@ -551,47 +346,17 @@ "@typescript-eslint/naming-convention": 1, "@typescript-eslint/no-unused-vars": 1 }, - "src/components/products/ProductQuickView.tsx": { - "@typescript-eslint/no-unused-vars": 16 - }, "src/components/products/ProductSparkline.tsx": { - "@typescript-eslint/no-unused-vars": 2, "react-hooks/exhaustive-deps": 1 }, - "src/components/products/ProductStickyHeader.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/products/ProductTableView.tsx": { "@typescript-eslint/no-explicit-any": 1, - "@typescript-eslint/no-unused-vars": 4, + "@typescript-eslint/no-unused-vars": 1, "react-hooks/exhaustive-deps": 1 }, - "src/components/products/QuickAddToQuote.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/products/RecentlyViewedBar.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/products/RelatedProducts.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/products/SalesHistoryChart.tsx": { - "@typescript-eslint/no-non-null-assertion": 2, - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/products/SimilarProducts.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/products/SingleVariantPicker.tsx": { - "@typescript-eslint/no-non-null-assertion": 1, - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/products/StockHistoryChart.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, - "src/components/products/SupplierComparisonCards.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, "src/components/products/VariantGridMatrix.tsx": { "@typescript-eslint/no-non-null-assertion": 1 }, @@ -599,19 +364,11 @@ "react-hooks/exhaustive-deps": 1 }, "src/components/products/customization/ConfigurationPanel.tsx": { - "@typescript-eslint/no-non-null-assertion": 1, - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/products/customization/LocationCard.tsx": { - "@typescript-eslint/no-non-null-assertion": 1, - "@typescript-eslint/no-unused-vars": 1 + "@typescript-eslint/no-non-null-assertion": 1 }, "src/components/products/customization/LocationPanel.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/components/products/customization/__tests__/LocationPanelAdvanced.test.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/products/customization/__tests__/LocationPanelPrice.test.tsx": { "@typescript-eslint/no-explicit-any": 1, "react-hooks/exhaustive-deps": 1 @@ -623,105 +380,59 @@ "@typescript-eslint/naming-convention": 1 }, "src/components/products/share/ShareAllColorsDialog.tsx": { - "@typescript-eslint/no-non-null-assertion": 1, - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/products/share/SharePreviewDialog.tsx": { "@typescript-eslint/no-non-null-assertion": 1 }, "src/components/products/share/usePhotoDownload.ts": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/products/useStockChartData.ts": { - "@typescript-eslint/no-non-null-assertion": 2, - "@typescript-eslint/no-unused-vars": 1, - "react-hooks/exhaustive-deps": 1 - }, "src/components/products/zoomable-gallery/useGalleryZoom.ts": { "react-hooks/exhaustive-deps": 1 }, "src/components/providers/AppBootstrap.tsx": { - "@typescript-eslint/no-unused-vars": 3 - }, - "src/components/quotes/AdminTemplatesManager.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/quotes/MarginInsightBadge.tsx": { - "@typescript-eslint/no-non-null-assertion": 4 - }, "src/components/quotes/PdfGenerationDialog.tsx": { - "@typescript-eslint/no-unused-vars": 11 + "@typescript-eslint/no-unused-vars": 4 }, "src/components/quotes/QuickQuoteFAB.tsx": { "@typescript-eslint/no-unused-vars": 2 }, "src/components/quotes/QuoteAutoSave.tsx": { - "@typescript-eslint/no-unused-vars": 3, + "@typescript-eslint/no-unused-vars": 1, "react-hooks/exhaustive-deps": 2 }, "src/components/quotes/QuoteBuilderNavigation.tsx": { "@typescript-eslint/consistent-type-imports": 1 }, "src/components/quotes/QuoteBuilderProductSearch.tsx": { - "@typescript-eslint/no-non-null-assertion": 1, - "@typescript-eslint/no-unused-vars": 1 + "@typescript-eslint/no-non-null-assertion": 1 }, "src/components/quotes/QuoteBuilderSummaryColumn.tsx": { - "@typescript-eslint/no-unused-vars": 6 + "@typescript-eslint/no-unused-vars": 1 }, "src/components/quotes/QuoteHistoryPanel.tsx": { "@typescript-eslint/no-unused-vars": 2, "react-hooks/exhaustive-deps": 1 }, - "src/components/quotes/QuoteKanbanBoard.tsx": { - "@typescript-eslint/no-non-null-assertion": 3, - "@typescript-eslint/no-unused-vars": 3 - }, - "src/components/quotes/QuoteProductColorSelector.tsx": { - "@typescript-eslint/no-non-null-assertion": 1, - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/quotes/QuoteProductCustomization.tsx": { "@typescript-eslint/no-explicit-any": 1 }, "src/components/quotes/QuoteSignaturePad.tsx": { "@typescript-eslint/no-non-null-assertion": 2 }, - "src/components/quotes/QuoteStatusTimeline.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, "src/components/quotes/QuoteTemplatesList.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/quotes/QuoteValidityBanner.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/quotes/QuoteVersionCompare.tsx": { - "@typescript-eslint/no-unused-vars": 2, + "@typescript-eslint/no-unused-vars": 1, "react-hooks/exhaustive-deps": 1 }, "src/components/quotes/QuoteVersionHistory.tsx": { - "@typescript-eslint/no-unused-vars": 2, "react-hooks/exhaustive-deps": 1 }, - "src/components/quotes/QuotesConfigurableList.tsx": { - "@typescript-eslint/no-non-null-assertion": 9, - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/quotes/SaveAsTemplateButton.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/quotes/__tests__/QuoteBuilderDiscount.test.tsx": { "@typescript-eslint/no-explicit-any": 1 }, - "src/components/quotes/__tests__/QuoteBuilderDiscountAdvanced.test.tsx": { - "@typescript-eslint/no-explicit-any": 1, - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/quotes/__tests__/QuoteBuilderStepper.test.tsx": { - "@typescript-eslint/consistent-type-imports": 1, - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/quotes/company-contact/CompanySearchDropdown.tsx": { "@typescript-eslint/no-explicit-any": 3 }, @@ -732,7 +443,7 @@ "@typescript-eslint/no-explicit-any": 4 }, "src/components/ramo-atividade/RamoAtividadeGroupAccordion.tsx": { - "@typescript-eslint/no-unused-vars": 2 + "@typescript-eslint/no-unused-vars": 1 }, "src/components/ramo-atividade/SegmentoCheckbox.tsx": { "@typescript-eslint/no-unused-vars": 1 @@ -743,9 +454,6 @@ "src/components/replenishments/ReplenishmentProductGrid.tsx": { "react-hooks/exhaustive-deps": 2 }, - "src/components/reports/ScheduledReportsManager.tsx": { - "@typescript-eslint/no-unused-vars": 3 - }, "src/components/search/AdvancedSearch.tsx": { "@typescript-eslint/no-unused-vars": 1, "react-hooks/exhaustive-deps": 1 @@ -754,15 +462,11 @@ "@typescript-eslint/no-unused-vars": 2, "react-hooks/exhaustive-deps": 1 }, - "src/components/search/GlobalSearchHelpers.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/search/GlobalSearchIdleState.tsx": { - "@typescript-eslint/no-unused-vars": 2 + "@typescript-eslint/no-unused-vars": 1 }, "src/components/search/GlobalSearchPalette.tsx": { "@typescript-eslint/no-explicit-any": 1, - "@typescript-eslint/no-unused-vars": 3, "react-hooks/exhaustive-deps": 4 }, "src/components/search/SmartSuggestions.tsx": { @@ -772,85 +476,50 @@ "react-hooks/exhaustive-deps": 1 }, "src/components/search/VoiceSearchOverlay.tsx": { - "@typescript-eslint/no-unused-vars": 1, "react-hooks/exhaustive-deps": 1 }, "src/components/search/VoiceSearchOverlayConnected.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/components/search/useGlobalSearch.ts": { - "@typescript-eslint/no-non-null-assertion": 2, - "@typescript-eslint/no-unused-vars": 5, - "react-hooks/exhaustive-deps": 5 - }, "src/components/search/voice/VoiceOverlaySections.tsx": { "@typescript-eslint/no-unused-vars": 3 }, - "src/components/security/PushNotificationSettings.tsx": { - "@typescript-eslint/no-unused-vars": 3 - }, "src/components/security/SecurityDashboard.tsx": { "@typescript-eslint/no-non-null-assertion": 2 }, "src/components/security/useSecurityData.ts": { "@typescript-eslint/no-non-null-assertion": 1 }, - "src/components/simulator/MockupPreview.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, - "src/components/simulator/NicheRecommendationBadge.tsx": { - "@typescript-eslint/no-non-null-assertion": 2, - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/simulator/ScenarioComparison.tsx": { "@typescript-eslint/no-unused-vars": 2 }, "src/components/simulator/TechniqueCard.tsx": { - "@typescript-eslint/no-unused-vars": 2 + "@typescript-eslint/no-unused-vars": 1 }, "src/components/simulator/wizard/ComparisonCard.tsx": { "@typescript-eslint/no-unused-vars": 2 }, - "src/components/simulator/wizard/ConfirmedSummary.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/simulator/wizard/MobilePersonalizationSummary.tsx": { "@typescript-eslint/no-unused-vars": 1 }, "src/components/simulator/wizard/PersonalizationSummary.tsx": { "@typescript-eslint/no-unused-vars": 1 }, - "src/components/simulator/wizard/PersonalizationTabs.tsx": { - "@typescript-eslint/no-unused-vars": 2 - }, "src/components/simulator/wizard/StepComparison.tsx": { "@typescript-eslint/no-unused-vars": 3 }, "src/components/simulator/wizard/StepLocation.tsx": { "@typescript-eslint/no-unused-vars": 2 }, - "src/components/simulator/wizard/StepProduct.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/simulator/wizard/StepSpecs.tsx": { - "@typescript-eslint/no-unused-vars": 1, "react-hooks/exhaustive-deps": 2 }, - "src/components/simulator/wizard/WizardContextBar.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/ui/ConfirmDialog.tsx": { "@typescript-eslint/naming-convention": 1 }, "src/components/ui/DataCard.tsx": { "@typescript-eslint/naming-convention": 1 }, - "src/components/ui/LoadingButton.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/components/ui/LoadingState.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/components/ui/ShortcutsHelpDialog.tsx": { "@typescript-eslint/naming-convention": 1 }, @@ -865,7 +534,7 @@ }, "src/contexts/AuthContext.tsx": { "@typescript-eslint/no-explicit-any": 2, - "@typescript-eslint/no-unused-vars": 7 + "@typescript-eslint/no-unused-vars": 3 }, "src/contexts/CollectionsContext.tsx": { "react-hooks/exhaustive-deps": 1 @@ -873,18 +542,12 @@ "src/contexts/ThemeContext.tsx": { "@typescript-eslint/no-explicit-any": 1 }, - "src/data/mock-match-products.ts": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/hooks/__tests__/useIPValidation.test.ts": { "@typescript-eslint/no-explicit-any": 1 }, "src/hooks/__tests__/useLoginAttempts.unit.test.tsx": { "@typescript-eslint/no-explicit-any": 1 }, - "src/hooks/__tests__/useQuoteItems.autoexpand.test.ts": { - "@typescript-eslint/no-explicit-any": 2 - }, "src/hooks/admin/useAdminKitTemplates.ts": { "@typescript-eslint/naming-convention": 1 }, @@ -894,18 +557,6 @@ "src/hooks/auth/useAccessSecurity.ts": { "@typescript-eslint/naming-convention": 5 }, - "src/hooks/auth/useProfileRoles.ts": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/hooks/bi/useClientOrdersHistory.ts": { - "@typescript-eslint/no-non-null-assertion": 1 - }, - "src/hooks/bi/useClientSeasonality.ts": { - "@typescript-eslint/no-non-null-assertion": 1 - }, - "src/hooks/bi/useClientVsIndustry.ts": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/hooks/common/useOrgData.ts": { "@typescript-eslint/no-explicit-any": 11 }, @@ -930,27 +581,15 @@ "src/hooks/intelligence/useMagicUpState.ts": { "react-hooks/exhaustive-deps": 2 }, - "src/hooks/kit-builder/useKitBuilder.ts": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/hooks/kit-builder/useKitBuilderPageState.ts": { "react-hooks/exhaustive-deps": 2 }, - "src/hooks/kit-builder/useKitBuilderQueries.ts": { - "@typescript-eslint/no-non-null-assertion": 3 - }, "src/hooks/kit-builder/useKitCollaboration.ts": { "react-hooks/exhaustive-deps": 1 }, - "src/hooks/kit-builder/useKitUndoRedo.ts": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/hooks/mockup/useMockupGenerator.ts": { "no-duplicate-imports": 1 }, - "src/hooks/mockup/useMockupTechniques.ts": { - "@typescript-eslint/no-non-null-assertion": 2 - }, "src/hooks/products/useAdvancedFilters.ts": { "react-hooks/exhaustive-deps": 1 }, @@ -970,19 +609,9 @@ "src/hooks/products/useProductBounds.ts": { "react-hooks/exhaustive-deps": 1 }, - "src/hooks/products/useProductFreshnessOverride.ts": { - "@typescript-eslint/no-non-null-assertion": 1 - }, - "src/hooks/products/useProductImages.ts": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/hooks/products/useProductMatch.ts": { "react-hooks/exhaustive-deps": 1 }, - "src/hooks/products/useProducts.ts": { - "@typescript-eslint/no-non-null-assertion": 1, - "@typescript-eslint/no-unused-vars": 1 - }, "src/hooks/products/useProductsByCategory.ts": { "react-hooks/exhaustive-deps": 1 }, @@ -992,53 +621,19 @@ "src/hooks/products/useSellerCarts.ts": { "@typescript-eslint/naming-convention": 1 }, - "src/hooks/products/useStockAlerts.integration.test.tsx": { - "@typescript-eslint/no-explicit-any": 3 - }, - "src/hooks/products/useSupplierComparison.ts": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/hooks/products/useSupplierFiscalData.ts": { "react-hooks/exhaustive-deps": 2 }, "src/hooks/products/useSuppliers.ts": { "react-hooks/exhaustive-deps": 1 }, - "src/hooks/products/useVariantStock.ts": { - "@typescript-eslint/no-non-null-assertion": 1, - "react-hooks/exhaustive-deps": 11 - }, - "src/hooks/quotes/useProdutoPersonalizacao.ts": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/hooks/quotes/useQuoteBuilderState.ts": { "no-duplicate-imports": 1, "react-hooks/exhaustive-deps": 7 }, - "src/hooks/quotes/useQuoteComments.ts": { - "@typescript-eslint/no-non-null-assertion": 2 - }, - "src/hooks/quotes/useQuoteFunnel.ts": { - "@typescript-eslint/no-non-null-assertion": 3 - }, - "src/hooks/quotes/useQuotes.ts": { - "@typescript-eslint/no-explicit-any": 8, - "@typescript-eslint/no-non-null-assertion": 2, - "@typescript-eslint/no-unused-vars": 1 - }, - "src/hooks/simulation/useExternalSimulator.ts": { - "@typescript-eslint/no-non-null-assertion": 2 - }, "src/hooks/simulation/useSimulation.ts": { "react-hooks/exhaustive-deps": 2 }, - "src/hooks/simulation/useTechniquePricingOptions.ts": { - "@typescript-eslint/no-non-null-assertion": 8, - "react-hooks/exhaustive-deps": 2 - }, - "src/hooks/simulator/useSimulatorWizard.ts": { - "react-hooks/exhaustive-deps": 15 - }, "src/hooks/simulator/useWizardPersistence.ts": { "react-hooks/exhaustive-deps": 1 }, @@ -1051,38 +646,19 @@ "src/lib/auth/auth-flow-tracer.ts": { "eqeqeq": 1 }, - "src/lib/bi/executive-summary.ts": { - "@typescript-eslint/no-non-null-assertion": 2 - }, "src/lib/error-reporter.ts": { "@typescript-eslint/naming-convention": 1, "@typescript-eslint/no-non-null-assertion": 1 }, - "src/lib/external-db/price-tables.ts": { - "@typescript-eslint/no-non-null-assertion": 6 - }, - "src/lib/external-db/products.ts": { - "@typescript-eslint/no-non-null-assertion": 7 - }, "src/lib/feature-flags.ts": { "@typescript-eslint/no-non-null-assertion": 1 }, - "src/lib/lazyWithRetry.ts": { - "@typescript-eslint/no-explicit-any": 1, - "@typescript-eslint/no-unused-vars": 1 - }, "src/lib/logger.ts": { "no-console": 3 }, "src/lib/personalization/adapters/raw-row.adapter.ts": { "@typescript-eslint/naming-convention": 1 }, - "src/lib/personalization/repositories/priceTable.repository.ts": { - "@typescript-eslint/no-non-null-assertion": 2 - }, - "src/lib/personalization/selectors.ts": { - "@typescript-eslint/no-non-null-assertion": 8 - }, "src/lib/print-area-grouping.ts": { "@typescript-eslint/no-non-null-assertion": 2 }, @@ -1105,35 +681,20 @@ "react-hooks/exhaustive-deps": 1 }, "src/pages/Simulation.tsx": { - "@typescript-eslint/no-explicit-any": 1, - "@typescript-eslint/no-unused-vars": 2 + "@typescript-eslint/no-explicit-any": 1 }, "src/pages/__tests__/QuoteBuilderDeliveryTooltip.test.tsx": { "@typescript-eslint/no-explicit-any": 1 }, - "src/pages/__tests__/SSOCallbackPage.test.tsx": { - "@typescript-eslint/consistent-type-imports": 1 - }, "src/pages/admin/AdminCadastrosPage.tsx": { "@typescript-eslint/no-non-null-assertion": 1 }, - "src/pages/admin/AdminClientPerformancePage.tsx": { - "@typescript-eslint/naming-convention": 1, - "@typescript-eslint/no-explicit-any": 1, - "@typescript-eslint/no-unused-vars": 1 - }, "src/pages/admin/AdminExternalDbPage.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/pages/admin/AdminTelemetriaPage.tsx": { - "@typescript-eslint/naming-convention": 1 - }, "src/pages/admin/PermissionsPage.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/pages/admin/RlsDenialsAdminPage.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/pages/admin/RolePermissionsPage.tsx": { "@typescript-eslint/no-non-null-assertion": 1, "react-hooks/exhaustive-deps": 1 @@ -1141,9 +702,6 @@ "src/pages/admin/RolesPage.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/pages/admin/SellerDiscountLimitsAdminPage.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/pages/admin/StorageTestPage.tsx": { "@typescript-eslint/no-explicit-any": 6, "react-hooks/exhaustive-deps": 1 @@ -1151,83 +709,32 @@ "src/pages/admin/telemetry/useOptimizationQueue.ts": { "react-hooks/exhaustive-deps": 5 }, - "src/pages/auth/Auth.tsx": { - "@typescript-eslint/no-unused-vars": 5, - "no-console": 4 - }, - "src/pages/auth/AuthBranding.tsx": { - "@typescript-eslint/no-unused-vars": 3 - }, - "src/pages/auth/AuthBranding.visual.test.tsx": { - "@typescript-eslint/no-explicit-any": 1 - }, - "src/pages/auth/ResetPassword.tsx": { - "@typescript-eslint/no-unused-vars": 4 - }, "src/pages/auth/SSOCallbackPage.tsx": { - "@typescript-eslint/consistent-type-imports": 1, - "@typescript-eslint/no-unused-vars": 1 + "@typescript-eslint/consistent-type-imports": 1 }, "src/pages/collections/CollectionDetailPage.tsx": { "@typescript-eslint/no-non-null-assertion": 1 }, - "src/pages/filters/useFiltersPageState.ts": { - "@typescript-eslint/no-non-null-assertion": 2 - }, "src/pages/kit-builder/KitLibraryPage.tsx": { "react-hooks/exhaustive-deps": 4 }, - "src/pages/kit-builder/useKitBuilderQuote.ts": { - "@typescript-eslint/no-non-null-assertion": 1 - }, "src/pages/mockups/MockupGenerator.tsx": { "@typescript-eslint/no-non-null-assertion": 1, "react-hooks/exhaustive-deps": 2 }, - "src/pages/mockups/MockupHistoryPage.tsx": { - "@typescript-eslint/no-non-null-assertion": 2 - }, "src/pages/products/FavoritesPage.tsx": { "no-duplicate-imports": 1, "react-hooks/exhaustive-deps": 1 }, - "src/pages/products/FiltersPage.tsx": { - "@typescript-eslint/no-non-null-assertion": 2 - }, "src/pages/products/ProductDetail.tsx": { "react-hooks/exhaustive-deps": 1 }, "src/pages/products/seller-carts/useSellerCartsPage.ts": { "react-hooks/exhaustive-deps": 3 }, - "src/pages/quotes/QuoteBuilderPage.tsx": { - "@typescript-eslint/no-non-null-assertion": 1, - "@typescript-eslint/no-unused-vars": 5 - }, - "src/pages/quotes/QuoteViewPage.tsx": { - "@typescript-eslint/no-non-null-assertion": 2 - }, - "src/pages/quotes/QuotesDashboardPage.tsx": { - "@typescript-eslint/no-non-null-assertion": 2 - }, - "src/pages/quotes/QuotesListPage.tsx": { - "@typescript-eslint/no-non-null-assertion": 1 - }, - "src/pages/quotes/quote-view/useQuoteViewData.ts": { - "react-hooks/exhaustive-deps": 1 - }, - "src/pages/quotes/quotes-dashboard/useQuotesDashboard.ts": { - "@typescript-eslint/no-non-null-assertion": 3 - }, - "src/pages/quotes/useQuotesListPage.ts": { - "@typescript-eslint/no-unused-vars": 2 - }, "src/pages/system/RateLimitDashboardPage.tsx": { "react-hooks/exhaustive-deps": 1 }, - "src/pages/system/SystemStatusPage.tsx": { - "react-hooks/exhaustive-deps": 1 - }, "src/pages/tools/AdvancedPriceSearchPage.tsx": { "@typescript-eslint/naming-convention": 2 }, @@ -1240,21 +747,9 @@ "src/pages/trends/TrendsKpiCards.tsx": { "@typescript-eslint/naming-convention": 1 }, - "src/routes/RoutePrefetcher.tsx": { - "@typescript-eslint/no-explicit-any": 1 - }, - "src/services/__tests__/productService.test.ts": { - "@typescript-eslint/no-explicit-any": 2 - }, "src/services/__tests__/quoteService.test.ts": { "@typescript-eslint/no-explicit-any": 3 }, - "src/services/authService.ts": { - "@typescript-eslint/no-unused-vars": 4 - }, - "src/services/productService.ts": { - "@typescript-eslint/no-non-null-assertion": 3 - }, "src/services/quoteService.ts": { "@typescript-eslint/no-explicit-any": 2 }, @@ -1263,28 +758,14 @@ "@typescript-eslint/no-explicit-any": 5, "no-console": 1 }, - "src/tests/AdminMobileInteraction.test.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, - "src/tests/AdminStructuralComparison.test.tsx": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/tests/CatalogFilteringLogic.test.tsx": { - "@typescript-eslint/no-explicit-any": 5, - "@typescript-eslint/no-unused-vars": 2 - }, - "src/tests/ScenarioSimulation.test.ts": { - "@typescript-eslint/no-unused-vars": 1 + "@typescript-eslint/no-explicit-any": 5 }, "src/tests/SidebarReorganized.test.tsx": { "@typescript-eslint/no-explicit-any": 1 }, - "src/tests/quotePersistence.test.ts": { - "@typescript-eslint/no-unused-vars": 3 - }, "src/types/jspdf-autotable.d.ts": { - "@typescript-eslint/naming-convention": 1, - "@typescript-eslint/no-unused-vars": 1 + "@typescript-eslint/naming-convention": 1 }, "src/types/stock.ts": { "@typescript-eslint/no-non-null-assertion": 1 @@ -1292,9 +773,6 @@ "src/utils/color-image-resolver.ts": { "@typescript-eslint/no-non-null-assertion": 4 }, - "src/utils/excelExport.ts": { - "@typescript-eslint/no-unused-vars": 1 - }, "src/utils/performance.ts": { "@typescript-eslint/no-unused-vars": 2, "no-console": 1 diff --git a/src/components/admin/connections/__tests__/ConnectionUI.test.tsx b/src/components/admin/connections/__tests__/ConnectionUI.test.tsx index 6db866893..de571cf78 100644 --- a/src/components/admin/connections/__tests__/ConnectionUI.test.tsx +++ b/src/components/admin/connections/__tests__/ConnectionUI.test.tsx @@ -60,13 +60,13 @@ describe('ConnectionsOverviewTable Interações e Acessibilidade', () => { beforeEach(() => { vi.clearAllMocks(); - useAuthMock.mockReturnValue({ isAdmin: true }); + useAuthMock.mockReturnValue({ isAdmin: true } as unknown as ReturnType); useConnectionsOverviewMock.mockReturnValue({ rows: mockRows, loading: false, refresh: vi.fn(), - }); - useConnectionTesterMock.mockReturnValue({ test: vi.fn(), testing: false }); + } as unknown as ReturnType); + useConnectionTesterMock.mockReturnValue({ test: vi.fn(), isTesting: false } as unknown as ReturnType); }); it('deve permitir focar e navegar nos botões de ação via teclado', () => { diff --git a/src/components/admin/connections/__tests__/ConnectionsOverviewTable.test.tsx b/src/components/admin/connections/__tests__/ConnectionsOverviewTable.test.tsx index 4b7e16e65..4b06d97a7 100644 --- a/src/components/admin/connections/__tests__/ConnectionsOverviewTable.test.tsx +++ b/src/components/admin/connections/__tests__/ConnectionsOverviewTable.test.tsx @@ -74,26 +74,26 @@ describe('ConnectionsOverviewTable Regression Tests', () => { beforeEach(() => { vi.clearAllMocks(); - useAuthMock.mockReturnValue({ isAdmin: true }); + useAuthMock.mockReturnValue({ isAdmin: true } as unknown as ReturnType); useConnectionsOverviewMock.mockReturnValue({ rows: mockRows, loading: false, refreshing: false, refresh: vi.fn(), patchRow: vi.fn(), - }); + } as unknown as ReturnType); useConnectionTesterMock.mockReturnValue({ test: vi.fn(), testing: false, - }); + } as unknown as ReturnType); useConsecutiveFailuresMock.mockReturnValue({ map: new Map(), loading: false, - }); + } as unknown as ReturnType); useSecretsManagerMock.mockReturnValue({ secrets: [], list: vi.fn(), - }); + } as unknown as ReturnType); }); it('should render the table with correct data', async () => { @@ -129,7 +129,7 @@ describe('ConnectionsOverviewTable Regression Tests', () => { refreshing: false, refresh: refreshMock, patchRow: vi.fn(), - }); + } as unknown as ReturnType); render( @@ -150,7 +150,7 @@ describe('ConnectionsOverviewTable Regression Tests', () => { refreshing: false, refresh: vi.fn(), patchRow: vi.fn(), - }); + } as unknown as ReturnType); render( diff --git a/src/components/auth/ForgotPasswordForm.tsx b/src/components/auth/ForgotPasswordForm.tsx index acce034e5..10200e241 100644 --- a/src/components/auth/ForgotPasswordForm.tsx +++ b/src/components/auth/ForgotPasswordForm.tsx @@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom'; import { z } from 'zod'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import { Mail, Loader2, ArrowLeft, Clock, ShieldCheck } from 'lucide-react'; +import { Mail, Loader2, ArrowLeft, Clock } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; @@ -56,7 +56,7 @@ export function ForgotPasswordForm({ onBack }: ForgotPasswordFormProps) { // Navega para a página de confirmação com instruções detalhadas navigate('/forgot-password-confirmation'); - } catch (error) { + } catch (_error) { toast({ variant: 'destructive', title: 'Erro inesperado', diff --git a/src/components/auth/PasswordStrengthIndicator.tsx b/src/components/auth/PasswordStrengthIndicator.tsx index 54e6c1b1f..a601dac9e 100644 --- a/src/components/auth/PasswordStrengthIndicator.tsx +++ b/src/components/auth/PasswordStrengthIndicator.tsx @@ -1,4 +1,4 @@ -import { useMemo, useEffect, useState, useCallback } from 'react'; +import { useMemo, useEffect, useState } from 'react'; import { cn } from '@/lib/utils'; import { Check, X, AlertTriangle, Loader2, Shield } from 'lucide-react'; import { usePasswordBreachCheck } from '@/hooks/auth'; diff --git a/src/components/bi/BIAiCopilot.tsx b/src/components/bi/BIAiCopilot.tsx index b20676026..c5cee9e8d 100644 --- a/src/components/bi/BIAiCopilot.tsx +++ b/src/components/bi/BIAiCopilot.tsx @@ -118,7 +118,7 @@ export function BIAiCopilot({ open, onOpenChange, clientId, clientName, ramoAtiv if (error) throw error; const answer = (data as { answer?: string })?.answer ?? "Não consegui processar agora."; setMessages((prev) => [...prev, { role: "assistant", content: answer }]); - } catch (e) { + } catch (_e) { toast.error("Erro ao consultar o copiloto. Tente novamente."); setMessages((prev) => [ ...prev, diff --git a/src/components/bi/ClientComparator.tsx b/src/components/bi/ClientComparator.tsx index 696fbc794..72943009e 100644 --- a/src/components/bi/ClientComparator.tsx +++ b/src/components/bi/ClientComparator.tsx @@ -4,7 +4,6 @@ */ import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { CheckCircle2, AlertTriangle, X, Trophy, Minus } from "lucide-react"; import { cn } from "@/lib/utils"; diff --git a/src/components/bi/ExecutiveSummaryButton.tsx b/src/components/bi/ExecutiveSummaryButton.tsx index 1f18a05e5..ceac92cb4 100644 --- a/src/components/bi/ExecutiveSummaryButton.tsx +++ b/src/components/bi/ExecutiveSummaryButton.tsx @@ -12,7 +12,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Copy, Loader2, FileText, Sparkles, Presentation } from "lucide-react"; +import { Copy, Loader2, Sparkles, Presentation } from "lucide-react"; import { toast } from "sonner"; import { useClientHealthScore } from "@/hooks/bi/useClientHealthScore"; import { useClientBI } from "@/hooks/bi/useClientBI"; diff --git a/src/components/catalog/CatalogContent.tsx b/src/components/catalog/CatalogContent.tsx index f050ecbcb..1d6813ac8 100644 --- a/src/components/catalog/CatalogContent.tsx +++ b/src/components/catalog/CatalogContent.tsx @@ -1,9 +1,5 @@ -import { useRef, useCallback, useEffect, useState, useMemo, memo, type RefObject } from 'react'; +import { memo, type RefObject } from 'react'; import type { ActiveColorFilter } from '@/utils/color-image-resolver'; -import { useVirtualizer } from '@tanstack/react-virtual'; -import { Loader2, ArrowUp, AlertCircle } from 'lucide-react'; -import { useProductsContextSafe } from '@/contexts/ProductsContext'; -import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; @@ -14,7 +10,6 @@ import { ProductCardSkeleton } from '@/components/products/ProductCardSkeleton'; import { ProductListItemSkeleton } from '@/components/products/ProductListItemSkeleton'; import { ProductTableSkeleton } from '@/components/products/ProductTableSkeleton'; import { EmptyState } from '@/components/common/EmptyState'; -import { SelectionCheckbox } from '@/components/common/SelectionCheckbox'; import { CatalogBulkModals } from './CatalogBulkModals'; import { useCatalogSelection } from './useCatalogSelection'; import { cn } from '@/lib/utils'; @@ -66,7 +61,7 @@ export const CatalogContent = memo(function CatalogContent({ isLoadingMore, totalEstimate, loadMoreRef, - itemsPerPage, + itemsPerPage: _itemsPerPage, navigate, handleViewProduct, handleShareProduct, @@ -76,13 +71,13 @@ export const CatalogContent = memo(function CatalogContent({ isInCompare, onToggleCompare, canAddToCompare, - onLoadMore, + onLoadMore: _onLoadMore, onResetFilters, selectionMode, onSelectedCountChange, activeColorFilter, - activeProductId, - setActiveProductId, + activeProductId: _activeProductId, + setActiveProductId: _setActiveProductId, }: CatalogContentProps) { const selection = useCatalogSelection( paginatedProducts, diff --git a/src/components/categories/CategorySidebarPanel.tsx b/src/components/categories/CategorySidebarPanel.tsx index 99cd04e83..106dce7b6 100644 --- a/src/components/categories/CategorySidebarPanel.tsx +++ b/src/components/categories/CategorySidebarPanel.tsx @@ -1,5 +1,5 @@ import { useState, useCallback, type MouseEvent } from 'react'; -import { ChevronRight, ChevronDown, Folder, FolderOpen, X, ChevronLeft, Layers } from 'lucide-react'; +import { ChevronRight, Folder, X, ChevronLeft, Layers } from 'lucide-react'; import { useCategoriesTree, type CategoryNode, type CategoryTreeItem } from '@/hooks/products'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; diff --git a/src/components/compare/AIComparisonAdvisor.tsx b/src/components/compare/AIComparisonAdvisor.tsx index 36d5e1cb8..01456d9bf 100644 --- a/src/components/compare/AIComparisonAdvisor.tsx +++ b/src/components/compare/AIComparisonAdvisor.tsx @@ -3,7 +3,7 @@ * Cache: sessionStorage por 30 min para combinação de IDs. */ import { useState } from "react"; -import type { Product } from "@/types/product"; +import type { Product } from "@/types/product-catalog"; import { Brain, Sparkles, Loader2, AlertCircle } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; diff --git a/src/components/compare/ComparisonDuelView.tsx b/src/components/compare/ComparisonDuelView.tsx index 6e92f6308..04201fc4d 100644 --- a/src/components/compare/ComparisonDuelView.tsx +++ b/src/components/compare/ComparisonDuelView.tsx @@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"; import { Crown, X, Check, Minus } from "lucide-react"; import { cn } from "@/lib/utils"; import { useComparisonScore } from "@/hooks/comparison"; -import type { Product } from "@/types/product"; +import type { Product } from "@/types/product-catalog"; interface Props { products: Product[]; diff --git a/src/components/compare/ComparisonHighlights.tsx b/src/components/compare/ComparisonHighlights.tsx index b785d7a36..259763a4d 100644 --- a/src/components/compare/ComparisonHighlights.tsx +++ b/src/components/compare/ComparisonHighlights.tsx @@ -4,7 +4,7 @@ */ import { useMemo } from "react"; import { cn } from "@/lib/utils"; -import { TrendingUp, TrendingDown, Minus, Crown, AlertTriangle } from "lucide-react"; +import { TrendingUp, Minus, Crown, AlertTriangle } from "lucide-react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; type HighlightType = "best" | "worst" | "neutral"; diff --git a/src/components/compare/ComparisonMobileView.tsx b/src/components/compare/ComparisonMobileView.tsx index 9d908716b..09414f331 100644 --- a/src/components/compare/ComparisonMobileView.tsx +++ b/src/components/compare/ComparisonMobileView.tsx @@ -3,7 +3,7 @@ * Cada linha = atributo, produtos viram chips horizontais swipeable. */ import { Badge } from "@/components/ui/badge"; -import type { Product, ProductColor } from "@/types/product"; +import type { Product, ProductColor } from "@/types/product-catalog"; import { Button } from "@/components/ui/button"; import { X, Crown } from "lucide-react"; import { cn } from "@/lib/utils"; diff --git a/src/components/compare/ComparisonPresentationLauncher.tsx b/src/components/compare/ComparisonPresentationLauncher.tsx index 262eb8867..7c0556979 100644 --- a/src/components/compare/ComparisonPresentationLauncher.tsx +++ b/src/components/compare/ComparisonPresentationLauncher.tsx @@ -3,7 +3,7 @@ * Atalhos: ← → navega, Esc fecha, F fullscreen do browser. */ import { useState, useEffect, useCallback } from "react"; -import type { Product } from "@/types/product"; +import type { Product } from "@/types/product-catalog"; import { Dialog, DialogContent } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; diff --git a/src/components/compare/ComparisonRadarChart.tsx b/src/components/compare/ComparisonRadarChart.tsx index 03e47fecd..6cef53aad 100644 --- a/src/components/compare/ComparisonRadarChart.tsx +++ b/src/components/compare/ComparisonRadarChart.tsx @@ -3,7 +3,7 @@ * Eixos: Preço (invertido), Estoque, Variedade de cores, Qtd mínima (invertido), Lead time (invertido). */ import { useMemo } from "react"; -import type { Product } from "@/types/product"; +import type { Product } from "@/types/product-catalog"; import { Radar, RadarChart, diff --git a/src/components/compare/FloatingCompareBar.tsx b/src/components/compare/FloatingCompareBar.tsx index 7cb4ee6d9..f10ebc3fd 100644 --- a/src/components/compare/FloatingCompareBar.tsx +++ b/src/components/compare/FloatingCompareBar.tsx @@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useComparisonStore, type CompareVariantInfo } from "@/stores/useComparisonStore"; import { useProductsContextSafe } from "@/contexts/ProductsContext"; -import type { Product } from "@/types/product"; +import type { Product } from "@/types/product-catalog"; import { cn } from "@/lib/utils"; export const FloatingCompareBar = React.forwardRef( diff --git a/src/components/dashboard/QuickActionsPanel.tsx b/src/components/dashboard/QuickActionsPanel.tsx index 4026d4b88..1f0ca94c6 100644 --- a/src/components/dashboard/QuickActionsPanel.tsx +++ b/src/components/dashboard/QuickActionsPanel.tsx @@ -1,22 +1,17 @@ -import { useEffect, useState, useMemo } from "react"; +import { useMemo } from "react"; import { Skeleton } from "@/components/ui/skeleton"; import { useNavigate } from "react-router-dom"; import { FilePlus, - Clock, - CheckCircle2, - XCircle, - TrendingUp, - FileText, - ArrowRight, - DollarSign, + Clock, TrendingUp, + FileText, DollarSign, Target, - BarChart3, + BarChart3 } from "lucide-react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { useQuotes, Quote } from "@/hooks/quotes"; +import { useQuotes } from "@/hooks/quotes"; import { useAuth } from "@/contexts/AuthContext"; import { startOfMonth, endOfMonth, parseISO, isWithinInterval } from "date-fns"; diff --git a/src/components/dashboard/RecentKitsWidget.tsx b/src/components/dashboard/RecentKitsWidget.tsx index af0b3d49b..ea1c2c6ba 100644 --- a/src/components/dashboard/RecentKitsWidget.tsx +++ b/src/components/dashboard/RecentKitsWidget.tsx @@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; -import { Package, Pencil, Clock, ArrowRight } from 'lucide-react'; +import { Package, Clock, ArrowRight } from 'lucide-react'; import { supabase } from '@/integrations/supabase/client'; import { useAuth } from '@/contexts/AuthContext'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; diff --git a/src/components/expert/ExpertChatDialog.tsx b/src/components/expert/ExpertChatDialog.tsx index d6d4d5af8..e80d501a9 100644 --- a/src/components/expert/ExpertChatDialog.tsx +++ b/src/components/expert/ExpertChatDialog.tsx @@ -3,7 +3,7 @@ * Original: 1418 lines → Now: ~80 lines (orchestrator only) */ import { Dialog, DialogContent } from "@/components/ui/dialog"; -import { FlowFilterPanel, defaultFlowFilters } from "./FlowFilterPanel"; +import { FlowFilterPanel } from "./FlowFilterPanel"; import { useExpertChat } from "./chat/useExpertChat"; import { ChatHeader } from "./chat/ChatHeader"; import { ChatHistoryPanel } from "./chat/ChatHistoryPanel"; diff --git a/src/components/filters/ColorGroupFilter.tsx b/src/components/filters/ColorGroupFilter.tsx index ec35c31c2..181ce76c8 100644 --- a/src/components/filters/ColorGroupFilter.tsx +++ b/src/components/filters/ColorGroupFilter.tsx @@ -15,7 +15,7 @@ import { CollapsibleTrigger, } from '@/components/ui/collapsible'; import { cn } from '@/lib/utils'; -import { useColorSystem, ColorGroup, ColorNuance, isLightColor } from '@/hooks/products'; +import { useColorSystem, isLightColor } from '@/hooks/products'; // ===================================================== // TIPOS diff --git a/src/components/filters/CommemorativeDateFilter.tsx b/src/components/filters/CommemorativeDateFilter.tsx index a5aeb4936..ce8eec754 100644 --- a/src/components/filters/CommemorativeDateFilter.tsx +++ b/src/components/filters/CommemorativeDateFilter.tsx @@ -1,7 +1,6 @@ import { useActiveCommemorativeDates, type CommemorativeDate } from "@/hooks/intelligence"; import { cn } from "@/lib/utils"; import { Skeleton } from "@/components/ui/skeleton"; -import { ScrollArea } from "@/components/ui/scroll-area"; interface CommemorativeDateFilterProps { selectedDates: string[]; diff --git a/src/components/filters/ExternalCategoryFilter.tsx b/src/components/filters/ExternalCategoryFilter.tsx index 2943600c3..c3489999e 100644 --- a/src/components/filters/ExternalCategoryFilter.tsx +++ b/src/components/filters/ExternalCategoryFilter.tsx @@ -3,7 +3,6 @@ import { ChevronDown, ChevronRight, Search, X, Layers, RefreshCw } from "lucide- import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; -import { ScrollArea } from "@/components/ui/scroll-area"; import { Skeleton } from "@/components/ui/skeleton"; import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; diff --git a/src/components/filters/FilterPanel.tsx b/src/components/filters/FilterPanel.tsx index 885d8b93d..e098574a6 100644 --- a/src/components/filters/FilterPanel.tsx +++ b/src/components/filters/FilterPanel.tsx @@ -26,7 +26,6 @@ import { QuickOptionsFilter, } from "./filter-panel/sections/SimpleFilters"; import { SizeFilter } from "./filter-panel/sections/SizeFilter"; -import { GenderBadge } from "@/components/products/GenderBadge"; export function FilterPanel({ filters, onFilterChange, onReset, activeFiltersCount, diff --git a/src/components/filters/filter-panel/sections/SizeFilter.tsx b/src/components/filters/filter-panel/sections/SizeFilter.tsx index b7a8c8d7e..47853e23d 100644 --- a/src/components/filters/filter-panel/sections/SizeFilter.tsx +++ b/src/components/filters/filter-panel/sections/SizeFilter.tsx @@ -3,10 +3,9 @@ * Extrai size_codes únicos das variações dos produtos carregados. */ import { useMemo, useState } from "react"; -import { Ruler, Search, X } from "lucide-react"; +import { Search, X } from "lucide-react"; import { cn } from "@/lib/utils"; import { Input } from "@/components/ui/input"; -import { Badge } from "@/components/ui/badge"; const SIZE_ORDER = [ "PP", "P", "M", "G", "GG", "XG", "XXG", "EG", "EGG", diff --git a/src/components/intelligence/IntelligenceFilterBar.tsx b/src/components/intelligence/IntelligenceFilterBar.tsx index 1e410e846..6b43f71d3 100644 --- a/src/components/intelligence/IntelligenceFilterBar.tsx +++ b/src/components/intelligence/IntelligenceFilterBar.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo, useCallback } from "react"; +import { useState, useMemo } from "react"; import { Filter, X, ChevronDown, Package, Search } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; diff --git a/src/components/intelligence/RankingFilterToolbar.tsx b/src/components/intelligence/RankingFilterToolbar.tsx index 87468cc6f..c7c88f133 100644 --- a/src/components/intelligence/RankingFilterToolbar.tsx +++ b/src/components/intelligence/RankingFilterToolbar.tsx @@ -1,5 +1,5 @@ -import { useState, useMemo, useCallback } from "react"; -import { Search, Filter, ChevronDown, X, Tag, Hash, History, Clock } from "lucide-react"; +import { useState, useMemo } from "react"; +import { Search, Filter, ChevronDown, X, Tag, History, Clock } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; diff --git a/src/components/intelligence/SegmentAnalysis.tsx b/src/components/intelligence/SegmentAnalysis.tsx index 51f7d27ce..361ddb8ce 100644 --- a/src/components/intelligence/SegmentAnalysis.tsx +++ b/src/components/intelligence/SegmentAnalysis.tsx @@ -2,7 +2,6 @@ import { PieChart, Building2 } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { useSegmentAnalysis } from "@/hooks/intelligence"; -import { cn } from "@/lib/utils"; export function SegmentAnalysis({ days = 30, categoryId, supplierId }: { days?: number; categoryId?: string | null; supplierId?: string | null }) { const { data: segments, isLoading } = useSegmentAnalysis(days, categoryId, supplierId); diff --git a/src/components/inventory/StockAlertDialogs.tsx b/src/components/inventory/StockAlertDialogs.tsx index 7cdc72a3d..0797919ff 100644 --- a/src/components/inventory/StockAlertDialogs.tsx +++ b/src/components/inventory/StockAlertDialogs.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { AlertTriangle, TrendingDown, X, CheckCircle2, Package, XCircle, Truck } from "lucide-react"; +import { AlertTriangle, TrendingDown, X, CheckCircle2, XCircle } from "lucide-react"; import { Dialog, DialogContent, diff --git a/src/components/inventory/StockCategoryTreeSelect.tsx b/src/components/inventory/StockCategoryTreeSelect.tsx index c7fc94248..00f424952 100644 --- a/src/components/inventory/StockCategoryTreeSelect.tsx +++ b/src/components/inventory/StockCategoryTreeSelect.tsx @@ -5,7 +5,6 @@ import { useState, useMemo } from "react"; import { ChevronRight, FolderTree, Search, X } from "lucide-react"; import { Input } from "@/components/ui/input"; -import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; import { useCategoriesTree, type CategoryNode } from "@/hooks/products"; import { motion } from "framer-motion"; diff --git a/src/components/inventory/StockDashboard.tsx b/src/components/inventory/StockDashboard.tsx index 7848add36..a4e16813f 100644 --- a/src/components/inventory/StockDashboard.tsx +++ b/src/components/inventory/StockDashboard.tsx @@ -100,7 +100,7 @@ export function StockDashboard() { return Math.round((healthy / summary.totalProducts) * 100); }, [summary]); - const healthColor = healthScore >= 80 ? 'text-success' : healthScore >= 50 ? 'text-warning' : 'text-destructive'; + const _healthColor = healthScore >= 80 ? 'text-success' : healthScore >= 50 ? 'text-warning' : 'text-destructive'; // Future stock total const futureStockTotal = useMemo( diff --git a/src/components/inventory/StockFilterToolbar.tsx b/src/components/inventory/StockFilterToolbar.tsx index 10de5dbf2..00aa22e31 100644 --- a/src/components/inventory/StockFilterToolbar.tsx +++ b/src/components/inventory/StockFilterToolbar.tsx @@ -58,7 +58,7 @@ interface StockFilterToolbarProps { filteredCount: number; } -const STATUS_OPTIONS: { value: StockStatus | 'all'; label: string; icon: React.ReactNode; color: string }[] = [ +const _STATUS_OPTIONS: { value: StockStatus | 'all'; label: string; icon: React.ReactNode; color: string }[] = [ { value: 'all', label: 'Todos', icon: , color: 'text-foreground' }, { value: 'in_stock', label: 'Em Estoque', icon: , color: 'text-success' }, { value: 'low_stock', label: 'Baixo', icon: , color: 'text-warning' }, diff --git a/src/components/kit-builder/BoxSelector.tsx b/src/components/kit-builder/BoxSelector.tsx index 85ca7168a..b0cbefaef 100644 --- a/src/components/kit-builder/BoxSelector.tsx +++ b/src/components/kit-builder/BoxSelector.tsx @@ -14,7 +14,7 @@ import { ScrollArea } from '@/components/ui/scroll-area'; import { Slider } from '@/components/ui/slider'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; +import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible'; import { cn } from '@/lib/utils'; import { formatVolume, diff --git a/src/components/kit-builder/DiscontinuedItemsAlert.tsx b/src/components/kit-builder/DiscontinuedItemsAlert.tsx index 7265c6424..04fbc1808 100644 --- a/src/components/kit-builder/DiscontinuedItemsAlert.tsx +++ b/src/components/kit-builder/DiscontinuedItemsAlert.tsx @@ -3,10 +3,9 @@ * Checks if any items in a kit are marked inactive and alerts the user */ -import { AlertTriangle, ExternalLink } from 'lucide-react'; +import { AlertTriangle } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; import type { KitItem } from '@/lib/kit-builder'; import { useQuery } from '@tanstack/react-query'; import { supabase } from '@/integrations/supabase/client'; diff --git a/src/components/kit-builder/KitIsometricPreview.tsx b/src/components/kit-builder/KitIsometricPreview.tsx index 6f7b5618b..3296385ac 100644 --- a/src/components/kit-builder/KitIsometricPreview.tsx +++ b/src/components/kit-builder/KitIsometricPreview.tsx @@ -211,7 +211,7 @@ export function KitIsometricPreview({ kitState, className }: KitIsometricPreview {/* Itens empilhados */} {sorted.map((p) => { - const c000 = iso(p.x, p.y, 0); + const _c000 = iso(p.x, p.y, 0); const c100 = iso(p.x + p.w, p.y, 0); const c101 = iso(p.x + p.w, p.y, p.d); const c001 = iso(p.x, p.y, p.d); diff --git a/src/components/kit-builder/KitSummary.tsx b/src/components/kit-builder/KitSummary.tsx index 40d09441c..c9bf9a18d 100644 --- a/src/components/kit-builder/KitSummary.tsx +++ b/src/components/kit-builder/KitSummary.tsx @@ -3,7 +3,6 @@ * Sub-components extracted to ./kit-summary/ */ import { Card, CardContent } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; import { AlertTriangle } from 'lucide-react'; import { KitMarginSimulator } from './KitMarginSimulator'; import { KitVisualPreview } from './KitVisualPreview'; diff --git a/src/components/kit-builder/SimilarKitsWidget.tsx b/src/components/kit-builder/SimilarKitsWidget.tsx index 2d3dd0ed9..93e267d18 100644 --- a/src/components/kit-builder/SimilarKitsWidget.tsx +++ b/src/components/kit-builder/SimilarKitsWidget.tsx @@ -6,7 +6,6 @@ import { useNavigate } from 'react-router-dom'; import * as Lucide from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { useSimilarKits } from '@/hooks/kit-builder'; import { formatCurrency } from '@/lib/kit-builder'; diff --git a/src/components/layout/GlobalOverlay.tsx b/src/components/layout/GlobalOverlay.tsx index 0fda77a54..82a0af46f 100644 --- a/src/components/layout/GlobalOverlay.tsx +++ b/src/components/layout/GlobalOverlay.tsx @@ -1,11 +1,9 @@ -import { Suspense, lazy } from "react"; +import { Suspense } from "react"; import { lazyWithRetry } from "@/lib/lazyWithRetry"; -import { Toaster } from "@/components/ui/toaster"; -import { Toaster as Sonner } from "@/components/ui/sonner"; // Lazy-loaded global components const OnboardingTour = lazyWithRetry(() => import("@/components/onboarding/OnboardingTour").then(m => ({ default: m.OnboardingTour }))); -const ExpertChatButton = lazyWithRetry(() => import("@/components/expert/ExpertChatButton").then(m => ({ default: m.ExpertChatButton }))); +const _ExpertChatButton = lazyWithRetry(() => import("@/components/expert/ExpertChatButton").then(m => ({ default: m.ExpertChatButton }))); const EnhancedSpotlight = lazyWithRetry(() => import("@/components/common/EnhancedSpotlight").then(m => ({ default: m.EnhancedSpotlight }))); const SmartMobileNav = lazyWithRetry(() => import("@/components/mobile/SmartMobileNav").then(m => ({ default: m.SmartMobileNav }))); const QuickQuoteFAB = lazyWithRetry(() => import("@/components/quotes/QuickQuoteFAB").then(m => ({ default: m.QuickQuoteFAB }))); diff --git a/src/components/layout/SidebarReorganized.tsx b/src/components/layout/SidebarReorganized.tsx index cf855ab62..84cf529a6 100644 --- a/src/components/layout/SidebarReorganized.tsx +++ b/src/components/layout/SidebarReorganized.tsx @@ -155,7 +155,7 @@ export const SidebarReorganized = React.memo( isCollapsed ? "4rem" : "16rem", ); }, [isCollapsed]); - const isItemActive = (href: string, exact?: boolean) => + const _isItemActive = (href: string, exact?: boolean) => isNavItemActive(location.pathname, href, exact); // Compute which groups should be auto-opened for the current route. diff --git a/src/components/mobile/MobileProductActions.tsx b/src/components/mobile/MobileProductActions.tsx index 60b1b9b51..d34b8b0c4 100644 --- a/src/components/mobile/MobileProductActions.tsx +++ b/src/components/mobile/MobileProductActions.tsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { Heart, Share2, Calculator, FileText, ShoppingCart } from "lucide-react"; +import { Heart, Share2, FileText } from "lucide-react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { useNavigate } from "react-router-dom"; diff --git a/src/components/mobile/SmartMobileNav.tsx b/src/components/mobile/SmartMobileNav.tsx index 3d52c8ddf..3aecd75f5 100644 --- a/src/components/mobile/SmartMobileNav.tsx +++ b/src/components/mobile/SmartMobileNav.tsx @@ -1,28 +1,19 @@ -import React, { useState, useEffect, forwardRef } from "react"; +import { useState, useEffect, forwardRef } from "react"; import { NavLink, useLocation, useNavigate } from "react-router-dom"; import { getPrefetchHandlers } from "@/lib/routePrefetch"; -import { - Home, - Package, - FileText, - +import { + Home, + Package, + FileText, + Plus, Heart, - Wand2, - BarChart3, - ShoppingCart, - Settings, - Sparkles, - Calculator, + Wand2, ShoppingCart, Calculator, FolderOpen, - X, - Sun, - Moon, + X } from "lucide-react"; -import { useTheme } from "@/contexts/ThemeContext"; import { cn } from "@/lib/utils"; import { motion, AnimatePresence } from "framer-motion"; -import { VisuallyHidden } from "@/components/a11y/VisuallyHidden"; interface NavItem { icon: typeof Home; @@ -172,7 +163,7 @@ export const SmartMobileNav = forwardRef(function SmartMobileNav style={{ paddingBottom: 'env(safe-area-inset-bottom, 0.5rem)' }} >
- {mainNavItems.map((item, index) => { + {mainNavItems.map((item, _index) => { const Icon = item.icon; const active = isActive(item.href); const isFab = item.href === "#fab"; diff --git a/src/components/mockup/LogoColorAnalyzer.tsx b/src/components/mockup/LogoColorAnalyzer.tsx index 195651688..1aeb1557f 100644 --- a/src/components/mockup/LogoColorAnalyzer.tsx +++ b/src/components/mockup/LogoColorAnalyzer.tsx @@ -12,7 +12,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover import { Input } from '@/components/ui/input'; import { ScrollArea } from '@/components/ui/scroll-area'; import { cn } from '@/lib/utils'; -import { searchPantone, type PantoneColor } from '@/data/pantone-coated'; +import { searchPantone } from '@/data/pantone-coated'; import type { DetectedColor } from '@/hooks/simulation'; interface LogoColorAnalyzerProps { diff --git a/src/components/mockup/MockupConfigPanel.tsx b/src/components/mockup/MockupConfigPanel.tsx index 283da2814..a4187cebf 100644 --- a/src/components/mockup/MockupConfigPanel.tsx +++ b/src/components/mockup/MockupConfigPanel.tsx @@ -10,9 +10,7 @@ import { Paintbrush, RefreshCw, Info, - ChevronDown, - FileText, - Wand2, + ChevronDown, Wand2 } from "lucide-react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; @@ -20,7 +18,6 @@ import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; -import { cn } from "@/lib/utils"; import { TechniqueTooltip } from "./TechniqueTooltip"; import { MockupClientSelector } from "./MockupClientSelector"; import { MockupProductSelector, type MockupProductSelection } from "./MockupProductSelector"; diff --git a/src/components/mockup/MockupProductSelector.tsx b/src/components/mockup/MockupProductSelector.tsx index 61dd5272f..3d0e82f5d 100644 --- a/src/components/mockup/MockupProductSelector.tsx +++ b/src/components/mockup/MockupProductSelector.tsx @@ -6,16 +6,15 @@ * Flow: Search products -> Select product -> Load full data -> Choose color/variant -> Confirmed. */ -import { useState, useMemo, useRef, useCallback, useEffect } from "react"; +import { useState, useMemo, useRef, useCallback } from "react"; import { useDebounce } from "@/hooks/common"; import { useVirtualizer } from "@tanstack/react-virtual"; -import { Search, Package, X, SearchX, Filter, Loader2 } from "lucide-react"; +import { Search, Package, X, SearchX, Filter } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; -import { cn } from "@/lib/utils"; import { useProductsCatalog, type ExternalVariantStock, type Product, type ProductLightweight } from "@/hooks/products"; import { ProductLoaderAndColorSelector } from "./MockupColorSelector"; diff --git a/src/components/mockup/MockupResultCard.tsx b/src/components/mockup/MockupResultCard.tsx index e1c06e65e..344ea1b95 100644 --- a/src/components/mockup/MockupResultCard.tsx +++ b/src/components/mockup/MockupResultCard.tsx @@ -15,7 +15,6 @@ import { X, ArrowLeftRight, } from "lucide-react"; -import { toast } from "sonner"; import confetti from "canvas-confetti"; import { Dialog, diff --git a/src/components/mockup/MockupWizard.tsx b/src/components/mockup/MockupWizard.tsx index b5f4a9a46..b07a542de 100644 --- a/src/components/mockup/MockupWizard.tsx +++ b/src/components/mockup/MockupWizard.tsx @@ -113,7 +113,7 @@ export const MockupWizard = forwardRef(functi style={{ width: `${progressPercent * 0.9}%` }} /> - {steps.map((step, index) => { + {steps.map((step, _index) => { // Allow clicking completed steps or the current active step's next const isClickable = onStepClick && (step.isCompleted || step.id <= currentStep); return ( diff --git a/src/components/navigation/DynamicBreadcrumbs.tsx b/src/components/navigation/DynamicBreadcrumbs.tsx index 980f99ba2..9a78ca497 100644 --- a/src/components/navigation/DynamicBreadcrumbs.tsx +++ b/src/components/navigation/DynamicBreadcrumbs.tsx @@ -65,7 +65,7 @@ const routeLabels: Record = { export function DynamicBreadcrumbs({ customItems, className }: DynamicBreadcrumbsProps) { const location = useLocation(); - const params = useParams(); + const _params = useParams(); const { isDev, isAdmin } = useAuth(); const breadcrumbs = useMemo(() => { diff --git a/src/components/notifications/NotificationDrawer.tsx b/src/components/notifications/NotificationDrawer.tsx index f2aecf226..643b9ca96 100644 --- a/src/components/notifications/NotificationDrawer.tsx +++ b/src/components/notifications/NotificationDrawer.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; -import { Bell, Check, CheckCheck, Trash2, Info, AlertTriangle, CheckCircle2, XCircle, ExternalLink, Loader2 } from "lucide-react"; +import { Bell, CheckCheck, Trash2, Info, AlertTriangle, CheckCircle2, XCircle, ExternalLink, Loader2 } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; diff --git a/src/components/notifications/badge-stats/EfficiencyGrid.tsx b/src/components/notifications/badge-stats/EfficiencyGrid.tsx index 3f7c41100..4455f7f0a 100644 --- a/src/components/notifications/badge-stats/EfficiencyGrid.tsx +++ b/src/components/notifications/badge-stats/EfficiencyGrid.tsx @@ -22,7 +22,7 @@ export function EfficiencyGrid({ coalescingByTrigger }: EfficiencyGridProps) { const ttlWithinPct = fetches === 0 ? 0 : Math.round((fetchesByTtlWindow.withinTtl / fetches) * 100); - const ttlAfterPct = fetches === 0 ? 0 : Math.round((fetchesByTtlWindow.afterTtl / fetches) * 100); + const _ttlAfterPct = fetches === 0 ? 0 : Math.round((fetchesByTtlWindow.afterTtl / fetches) * 100); return (
diff --git a/src/components/notifications/badge-stats/useNotificationsMetricsPanel.ts b/src/components/notifications/badge-stats/useNotificationsMetricsPanel.ts index d2938f31b..e048cedef 100644 --- a/src/components/notifications/badge-stats/useNotificationsMetricsPanel.ts +++ b/src/components/notifications/badge-stats/useNotificationsMetricsPanel.ts @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef, useState } from "react"; -import { notificationsMetrics, type BadgeRenderStat, type TriggerSource } from "@/lib/notifications-metrics"; +import { notificationsMetrics } from "@/lib/notifications-metrics"; /** Sliding-window length for the sparkline (60 samples × 1s = 60s). */ const SPARK_WINDOW_SECONDS = 60; diff --git a/src/components/onboarding/OnboardingTour.tsx b/src/components/onboarding/OnboardingTour.tsx index f8cf38ffe..98399f0eb 100644 --- a/src/components/onboarding/OnboardingTour.tsx +++ b/src/components/onboarding/OnboardingTour.tsx @@ -1,12 +1,9 @@ import { useEffect, useState, useCallback } from "react"; import { motion, AnimatePresence } from "framer-motion"; -import { X, ChevronLeft, ChevronRight, SkipForward, Sparkles, Play, Pause } from "lucide-react"; +import { X, ChevronLeft, ChevronRight, SkipForward, Sparkles } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { Progress } from "@/components/ui/progress"; -import { ONBOARDING_STEPS } from "@/hooks/ui"; import { useOnboardingContext } from "@/contexts/OnboardingContext"; import { useNavigate, useLocation } from "react-router-dom"; -import { cn } from "@/lib/utils"; interface TooltipPosition { top?: number; left?: number; diff --git a/src/components/pdf/PropostaComercialTailwind.tsx b/src/components/pdf/PropostaComercialTailwind.tsx index 86eabde8e..86b59fcfa 100644 --- a/src/components/pdf/PropostaComercialTailwind.tsx +++ b/src/components/pdf/PropostaComercialTailwind.tsx @@ -35,7 +35,7 @@ const PAGE_H = 1123; const FIRST_HEADER_H = 128; const CONT_HEADER_H = 60; const CONT_CLIENT_H = 60; // compact client bar on continuation pages -const FULL_FOOTER_H = 220; // last page: totals + signature + notes + green bar +const _FULL_FOOTER_H = 220; // last page: totals + signature + notes + green bar const SIMPLE_FOOTER_H = 30; // page number + green bar only const NOTES_FOOTER_H = 230; // notes block (conditions + terms) on every page const CONTENT_PAD = 36; diff --git a/src/components/pdf/proposal/LogoWithTransparentBg.tsx b/src/components/pdf/proposal/LogoWithTransparentBg.tsx index 4b8be035d..781e0d609 100644 --- a/src/components/pdf/proposal/LogoWithTransparentBg.tsx +++ b/src/components/pdf/proposal/LogoWithTransparentBg.tsx @@ -17,7 +17,7 @@ export function processLogoTransparent(src: string): Promise { .then((res) => res.blob()) .then( (blob) => - new Promise((resolve, reject) => { + new Promise((resolve, _reject) => { const objectUrl = URL.createObjectURL(blob); const img = new Image(); img.onload = () => { diff --git a/src/components/pricing/ProductPriceSimulator.tsx b/src/components/pricing/ProductPriceSimulator.tsx index 097842386..8dff517a4 100644 --- a/src/components/pricing/ProductPriceSimulator.tsx +++ b/src/components/pricing/ProductPriceSimulator.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useMemo, useEffect } from 'react'; +import { useState, useCallback, useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { Button } from '@/components/ui/button'; diff --git a/src/components/pricing/simulator/EngravingList.tsx b/src/components/pricing/simulator/EngravingList.tsx index 49c3ae726..55a4f3f4e 100644 --- a/src/components/pricing/simulator/EngravingList.tsx +++ b/src/components/pricing/simulator/EngravingList.tsx @@ -4,7 +4,6 @@ import { Badge } from '@/components/ui/badge'; import { Plus, Trash2, Paintbrush, Palette, Ruler, GripVertical } from 'lucide-react'; import { cn } from '@/lib/utils'; import type { ConfiguredEngraving } from "./types"; -import { formatCurrency } from './utils'; interface EngravingListProps { engravings: ConfiguredEngraving[]; diff --git a/src/components/pricing/simulator/MultiEngravingResult.tsx b/src/components/pricing/simulator/MultiEngravingResult.tsx index 5b0d0f6bb..b28f2c287 100644 --- a/src/components/pricing/simulator/MultiEngravingResult.tsx +++ b/src/components/pricing/simulator/MultiEngravingResult.tsx @@ -7,18 +7,18 @@ * - Código de orçamento automático */ -import { useMemo, useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Slider } from '@/components/ui/slider'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; -import { - Calculator, - Clock, - AlertCircle, - Package, +import { + Calculator, + Clock, + AlertCircle, + Package, Paintbrush, Copy, CheckCircle2, diff --git a/src/components/pricing/simulator/PriceResultV51.tsx b/src/components/pricing/simulator/PriceResultV51.tsx index 8f5b6949c..b13968b45 100644 --- a/src/components/pricing/simulator/PriceResultV51.tsx +++ b/src/components/pricing/simulator/PriceResultV51.tsx @@ -8,16 +8,14 @@ * - Código de orçamento: {TECNICA_CURTO}01-{FAIXA}-{AREA}-{CORES} */ -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; -import { - Calculator, - Clock, - TrendingDown, - AlertCircle, - Package, +import { + Calculator, + Clock, + TrendingDown, Package, Paintbrush, Copy, CheckCircle2, diff --git a/src/components/pricing/simulator/QuantityAndResult.tsx b/src/components/pricing/simulator/QuantityAndResult.tsx index e94669897..7867e2ad5 100644 --- a/src/components/pricing/simulator/QuantityAndResult.tsx +++ b/src/components/pricing/simulator/QuantityAndResult.tsx @@ -7,28 +7,25 @@ * - Código de orçamento automático */ -import { useMemo, useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Slider } from '@/components/ui/slider'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import { - Calculator, - Clock, - TrendingDown, - AlertCircle, - Copy, +import { + Calculator, + Clock, + TrendingDown, + AlertCircle, + Copy, CheckCircle2, Info, Loader2 } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { cn } from '@/lib/utils'; -import { - useCustomizationPriceLegacy, - useFaixasPrecoOficial, - type CustomizationPriceV2 +import { + useCustomizationPriceLegacy, type CustomizationPriceV2 } from '@/hooks/simulation'; import { formatCurrency, formatNumber } from './utils'; import type { Product, ProductTechnique } from "./types"; diff --git a/src/components/products/BulkActionBar.tsx b/src/components/products/BulkActionBar.tsx index b8b1afc4a..7bbd2dae4 100644 --- a/src/components/products/BulkActionBar.tsx +++ b/src/components/products/BulkActionBar.tsx @@ -10,7 +10,7 @@ * - Responsivo: labels escondidos em mobile, apenas ícones */ import { memo } from "react"; -import { Heart, GitCompare, FolderPlus, X, CheckSquare, ShoppingBag, FileText, Sparkles, FileDown } from "lucide-react"; +import { Heart, GitCompare, FolderPlus, X, CheckSquare, ShoppingBag, FileText, FileDown } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; diff --git a/src/components/products/FutureStockModal.tsx b/src/components/products/FutureStockModal.tsx index cfc01ec08..68e00e13b 100644 --- a/src/components/products/FutureStockModal.tsx +++ b/src/components/products/FutureStockModal.tsx @@ -1,13 +1,13 @@ import { useState, useMemo } from "react"; import { format, parseISO, addDays, isAfter, isBefore } from "date-fns"; import { ptBR } from "date-fns/locale"; -import { CalendarClock, Package, Truck, AlertTriangle, Calendar, Loader2, TrendingUp, ArrowUpDown, Filter, Clock } from "lucide-react"; +import { CalendarClock, Package, Truck, AlertTriangle, Calendar, TrendingUp, ArrowUpDown, Filter, Clock } from "lucide-react"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { cn } from "@/lib/utils"; -import { +import { useProductVariantsWithStock, processStockEntries, calculateColorSummary, diff --git a/src/components/products/PackagingModal.tsx b/src/components/products/PackagingModal.tsx index b13df1d1a..d2adfc443 100644 --- a/src/components/products/PackagingModal.tsx +++ b/src/components/products/PackagingModal.tsx @@ -2,7 +2,7 @@ * PackagingModal - Modal com detalhes da embalagem especial do produto */ import { useState } from "react"; -import { Gift, Package, Ruler, Scale, Boxes, Info, X } from "lucide-react"; +import { Gift, Package, Ruler, Scale, Boxes, Info } from "lucide-react"; import { Dialog, DialogContent, diff --git a/src/components/products/ProductCard.tsx b/src/components/products/ProductCard.tsx index 886674684..9ef99932e 100644 --- a/src/components/products/ProductCard.tsx +++ b/src/components/products/ProductCard.tsx @@ -8,7 +8,6 @@ import { Building2, Package } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { useQueryClient } from "@tanstack/react-query"; import { getCdnUrl, getSrcSet } from "@/utils/image-utils"; -import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { useProductBounds, type ExternalVariantStock, type Product, usePrefetchProduct } from "@/hooks/products"; import { toast } from "sonner"; @@ -59,7 +58,7 @@ export const ProductCard = memo(forwardRef(functi priority = false, }, ref) { const navigate = useNavigate(); - const queryClient = useQueryClient(); + const _queryClient = useQueryClient(); const { prefetchProduct } = usePrefetchProduct(); const [isHovered, setIsHovered] = useState(false); const [collectionModalOpen, setCollectionModalOpen] = useState(false); diff --git a/src/components/products/ProductGallery.tsx b/src/components/products/ProductGallery.tsx index 5df2a10ff..eaf7a89f4 100644 --- a/src/components/products/ProductGallery.tsx +++ b/src/components/products/ProductGallery.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useCallback, useEffect, useMemo } from "react"; +import { useState, useRef, useCallback, useEffect } from "react"; import { ChevronLeft, ChevronRight, Play, Maximize2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; diff --git a/src/components/products/ProductGrid.test.tsx b/src/components/products/ProductGrid.test.tsx index 33b1b643c..055b5cc34 100644 --- a/src/components/products/ProductGrid.test.tsx +++ b/src/components/products/ProductGrid.test.tsx @@ -45,7 +45,7 @@ vi.mock('@/components/products/share/SharePreviewDialog', () => ({ // Mock CDN and image utility vi.mock('@/utils/cdn-utils', () => ({ getCdnUrl: (url: string) => url, - getSrcSet: (url: string) => undefined, + getSrcSet: (_url: string) => undefined, })); const queryClient = new QueryClient({ diff --git a/src/components/products/ProductGrid.tsx b/src/components/products/ProductGrid.tsx index 255ccf073..eeaf6edf8 100644 --- a/src/components/products/ProductGrid.tsx +++ b/src/components/products/ProductGrid.tsx @@ -6,7 +6,6 @@ import { useReducedMotion } from "@/hooks/ui"; import { SelectionCheckbox } from "@/components/common/SelectionCheckbox"; import { cn } from "@/lib/utils"; import { ProductCardSkeleton } from "./ProductCardSkeleton"; -import type { ColumnCount } from "./ColumnSelector"; export interface ProductGridProps { products: Product[]; diff --git a/src/components/products/ProductListItem.tsx b/src/components/products/ProductListItem.tsx index 1ff4227fc..178d28e5b 100644 --- a/src/components/products/ProductListItem.tsx +++ b/src/components/products/ProductListItem.tsx @@ -13,14 +13,12 @@ * Favoritar, Comparar, Coleção, Share, Orçamento, Carrinho, QuickView */ import { memo, useState, useCallback, useRef, useEffect } from "react"; -import { Heart, GitCompare, Share2, Package, Building2, FolderPlus, Eye, FileText } from "lucide-react"; +import { Package, Building2 } from "lucide-react"; import { NoveltyBadge } from "./NoveltyBadge"; import { ListItemActions } from "./list-item/ListItemActions"; import { useNavigate } from "react-router-dom"; import { getCdnUrl } from "@/utils/image-utils"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { isLightColor, type ExternalVariantStock, type Product } from "@/hooks/products"; import { toast } from "sonner"; @@ -31,7 +29,6 @@ import { resolveHighlightHex } from "@/utils/color-group-hex"; import { PriceFreshnessBadge } from "./PriceFreshnessBadge"; import { resolveAllMatchingColors } from "@/utils/color-variant-carousel"; import { showUndoToast, showErrorToast } from "@/utils/undoToast"; -import { QuickAddToQuote } from "./QuickAddToQuote"; import { AddToCollectionModal } from "@/components/collections/AddToCollectionModal"; import { ProductQuickView } from "./ProductQuickView"; import { SharePreviewDialog } from "./share/SharePreviewDialog"; diff --git a/src/components/products/ProductQuickView.tsx b/src/components/products/ProductQuickView.tsx index 3417bd0f2..3ef1742f0 100644 --- a/src/components/products/ProductQuickView.tsx +++ b/src/components/products/ProductQuickView.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo, useCallback, useEffect, forwardRef } from "react"; +import { useState, useMemo, useEffect, forwardRef } from "react"; // framer-motion removido — transição via CSS animate-fade-in import { Heart, @@ -6,27 +6,17 @@ import { Share2, ShoppingCart, Package, - Truck, - ChevronLeft, - ChevronRight, - ExternalLink, - Sparkles, - Layers, - Plus, - Minus, - Ruler, - Weight, - ImageOff, + Truck, ExternalLink, Plus, + Minus } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { VisuallyHidden } from "@/components/a11y/VisuallyHidden"; import { cn } from "@/lib/utils"; -import { useProductImages, type Product, type ProductImage } from "@/hooks/products"; +import { useProductImages, type Product } from "@/hooks/products"; import { ProductCategoryBadges } from "./ProductCategoryBadges"; import { QuickViewGallery } from "./quick-view/QuickViewGallery"; import { ProductColorSelector, type ProductColor } from "./ProductColorSelector"; @@ -61,7 +51,7 @@ export const ProductQuickView = forwardRef(null); // imageLoaded removido — transição instantânea sem skeleton intermediário - const [imageError, setImageError] = useState(false); + const [_imageError, setImageError] = useState(false); // Hook: buscar imagens do produto via BD externo (Briefing v3) const { data: productImages = [] } = useProductImages(open && product ? product.id : null); @@ -168,22 +158,22 @@ export const ProductQuickView = forwardRef { + const _handlePrevImage = () => { setImageError(false); setCurrentImageIndex((prev) => prev === 0 ? displayImages.length - 1 : prev - 1 ); }; - const handleNextImage = () => { + const _handleNextImage = () => { setImageError(false); setCurrentImageIndex((prev) => prev === displayImages.length - 1 ? 0 : prev + 1 diff --git a/src/components/products/ProductSparkline.tsx b/src/components/products/ProductSparkline.tsx index 810a410c8..d8b2f0cd2 100644 --- a/src/components/products/ProductSparkline.tsx +++ b/src/components/products/ProductSparkline.tsx @@ -1,7 +1,7 @@ import { useMemo, useRef, useState, useCallback } from "react"; import { cn } from "@/lib/utils"; import { useSparklineData } from "@/hooks/intelligence"; -import { TrendingUp, TrendingDown, Minus, BarChart3, Zap, Activity } from "lucide-react"; +import { TrendingUp, TrendingDown, Minus, Zap, Activity } from "lucide-react"; interface ProductSparklineProps { productId: string; @@ -89,7 +89,7 @@ export function ProductSparkline({ productId, className }: ProductSparklineProps setTooltipPos({ x: e.clientX - rect.left, y: e.clientY - rect.top }); }, [points.length]); - const handleMouseEnter = useCallback((e: React.MouseEvent) => { + const handleMouseEnter = useCallback((_e: React.MouseEvent) => { if (hoverIndex === null) { const container = containerRef.current; if (!container) return; diff --git a/src/components/products/ProductStickyHeader.tsx b/src/components/products/ProductStickyHeader.tsx index 7d69b21a0..35a5ff23c 100644 --- a/src/components/products/ProductStickyHeader.tsx +++ b/src/components/products/ProductStickyHeader.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { motion, AnimatePresence } from "framer-motion"; -import { ShoppingCart, Heart, FileText } from "lucide-react"; +import { Heart, FileText } from "lucide-react"; import { Button } from "@/components/ui/button"; import { QuickAddToQuote } from "./QuickAddToQuote"; import { BulkVariantWizard } from "@/components/catalog/BulkVariantWizard"; diff --git a/src/components/products/ProductTableView.tsx b/src/components/products/ProductTableView.tsx index 19ca3d306..5fd7988ee 100644 --- a/src/components/products/ProductTableView.tsx +++ b/src/components/products/ProductTableView.tsx @@ -8,13 +8,12 @@ * ✅ PERFORMANCE 10/10: Virtualização implementada para suportar 15.000+ itens. */ import { memo, useState, useCallback, useMemo, useRef, useEffect } from "react"; -import { ArrowUpDown, ArrowUp, ArrowDown, Package, Loader2, Check } from "lucide-react"; +import { ArrowUpDown, ArrowUp, ArrowDown, Package, Loader2 } from "lucide-react"; import { useVirtualizer } from "@tanstack/react-virtual"; import { TableRowActions } from "./table-view/TableRowActions"; import { resolveColorImage, resolveColorStock, getActiveColorName, type ActiveColorFilter } from "@/utils/color-image-resolver"; import { resolveHighlightHex } from "@/utils/color-group-hex"; import { useNavigate } from "react-router-dom"; -import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; @@ -262,7 +261,7 @@ export const ProductTableView = memo(function ProductTableView({ const displayStatus = colorStock?.stockStatus ?? product.stockStatus; const activeColorName = getActiveColorName(product, activeColorFilter); const isSelected = selectionMode && selectedIds?.has(product.id); - const matchedColor = resolveHighlightHex(product.colors, activeColorFilter, highlightColors); + const _matchedColor = resolveHighlightHex(product.colors, activeColorFilter, highlightColors); return (
1; - const sharePercent = bestVelocity > 0 + const _sharePercent = bestVelocity > 0 ? Math.round((v.avg_daily_depletion_7d / bestVelocity) * 100) : 0; diff --git a/src/components/products/customization/ConfigurationPanel.tsx b/src/components/products/customization/ConfigurationPanel.tsx index 760f9de10..8d443da5c 100644 --- a/src/components/products/customization/ConfigurationPanel.tsx +++ b/src/components/products/customization/ConfigurationPanel.tsx @@ -9,7 +9,6 @@ import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { Loader2, Palette, Clock, Ruler, AlertCircle, Check } from "lucide-react"; -import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; diff --git a/src/components/products/customization/__tests__/LocationPanelAdvanced.test.tsx b/src/components/products/customization/__tests__/LocationPanelAdvanced.test.tsx index 48d3674a9..3cdc5a07d 100644 --- a/src/components/products/customization/__tests__/LocationPanelAdvanced.test.tsx +++ b/src/components/products/customization/__tests__/LocationPanelAdvanced.test.tsx @@ -4,7 +4,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, within, fireEvent } from "@testing-library/react"; -import { toast } from "sonner"; import { LocationPanel } from "../LocationPanel"; import type { GravacaoLocation, TechniqueOption, CustomizationPriceResponseV6 } from "@/types/customization"; diff --git a/src/components/products/share/ShareAllColorsDialog.tsx b/src/components/products/share/ShareAllColorsDialog.tsx index 627ef1db8..3d5a80951 100644 --- a/src/components/products/share/ShareAllColorsDialog.tsx +++ b/src/components/products/share/ShareAllColorsDialog.tsx @@ -1,5 +1,5 @@ import { useState, useCallback, useMemo } from "react"; -import { Palette, Send, Check, X, Eye, Pencil } from "lucide-react"; +import { Palette, Send, Check, Eye, Pencil } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Dialog, diff --git a/src/components/products/share/usePhotoDownload.ts b/src/components/products/share/usePhotoDownload.ts index 79840b40b..bd4599edd 100644 --- a/src/components/products/share/usePhotoDownload.ts +++ b/src/components/products/share/usePhotoDownload.ts @@ -54,7 +54,7 @@ export function usePhotoDownload() { title: "Download concluído", description: `${images.length} foto(s) baixada(s)`, }); - } catch (err) { + } catch (_err) { toast({ title: "Erro no download", description: "Não foi possível baixar as fotos", diff --git a/src/components/providers/AppBootstrap.tsx b/src/components/providers/AppBootstrap.tsx index 6c5587a58..8f199c09f 100644 --- a/src/components/providers/AppBootstrap.tsx +++ b/src/components/providers/AppBootstrap.tsx @@ -1,7 +1,6 @@ -import { type ReactNode, useEffect, useState, useCallback } from "react"; +import { type ReactNode, useEffect, useState } from "react"; import { useAuth } from "@/contexts/AuthContext"; import { supabase } from "@/integrations/supabase/client"; -import { useOnboardingContext } from "@/contexts/OnboardingContext"; import { AlertTriangle, RefreshCw } from "lucide-react"; import { Button } from "@/components/ui/button"; diff --git a/src/components/quotes/AdminTemplatesManager.tsx b/src/components/quotes/AdminTemplatesManager.tsx index 0bed4e8bf..e0e0c6fbb 100644 --- a/src/components/quotes/AdminTemplatesManager.tsx +++ b/src/components/quotes/AdminTemplatesManager.tsx @@ -6,7 +6,6 @@ import { useState, useMemo } from "react"; import { formatCurrency } from "@/lib/format"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; diff --git a/src/components/quotes/PdfGenerationDialog.tsx b/src/components/quotes/PdfGenerationDialog.tsx index 0aacdbb8f..ec293519c 100644 --- a/src/components/quotes/PdfGenerationDialog.tsx +++ b/src/components/quotes/PdfGenerationDialog.tsx @@ -5,7 +5,7 @@ */ import { useState, useCallback, useRef } from "react"; -import { Download, FileText, Eye, Loader2, Check, Send, Copy, Link2, MessageCircle, Mail, Printer } from "lucide-react"; +import { Download, FileText, Loader2, Check, Printer } from "lucide-react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; @@ -104,7 +104,7 @@ export function PdfGenerationDialog({ setPdfVersion((v) => v + 1); }; - const handleEmail = () => { + const _handleEmail = () => { const subject = encodeURIComponent(`Proposta Comercial ${quoteNumber || ""}`); const body = encodeURIComponent( `Olá,\n\nSegue a proposta comercial ${quoteNumber || ""}.\n\nQualquer dúvida, estou à disposição!\n\nAtt.` diff --git a/src/components/quotes/QuoteAutoSave.tsx b/src/components/quotes/QuoteAutoSave.tsx index d35606267..f9969f968 100644 --- a/src/components/quotes/QuoteAutoSave.tsx +++ b/src/components/quotes/QuoteAutoSave.tsx @@ -7,7 +7,6 @@ import { useState, useEffect, useCallback, useRef } from "react"; import { Cloud, CloudOff, Check, Loader2, AlertCircle } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, @@ -164,7 +163,7 @@ export function QuoteAutoSave({ } }, [storageKey, quoteId]); - const handleDiscard = () => { + const _handleDiscard = () => { localStorage.removeItem(storageKey); const keysToRemove: string[] = []; diff --git a/src/components/quotes/QuoteBuilderProductSearch.tsx b/src/components/quotes/QuoteBuilderProductSearch.tsx index 263bd0b6e..3f0347e9a 100644 --- a/src/components/quotes/QuoteBuilderProductSearch.tsx +++ b/src/components/quotes/QuoteBuilderProductSearch.tsx @@ -4,7 +4,6 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { AlertTriangle, Package, PackageCheck, Search, X } from "lucide-react"; import { cn } from "@/lib/utils"; diff --git a/src/components/quotes/QuoteBuilderSummaryColumn.tsx b/src/components/quotes/QuoteBuilderSummaryColumn.tsx index 11204e1f2..3150732a8 100644 --- a/src/components/quotes/QuoteBuilderSummaryColumn.tsx +++ b/src/components/quotes/QuoteBuilderSummaryColumn.tsx @@ -5,16 +5,14 @@ import { useState, useMemo } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; import { CurrencyInput } from "@/components/ui/currency-input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; -import { AlertTriangle, Edit, Loader2, Package, Percent, Save, Send, Shield, ShoppingCart, Trash2, CheckCircle2, X } from "lucide-react"; +import { AlertTriangle, Edit, Loader2, Package, Save, Send, Shield, ShoppingCart, Trash2, CheckCircle2, X } from "lucide-react"; import { cn } from "@/lib/utils"; import type { QuoteItem } from "@/hooks/quotes"; import { NegotiationMarkupCard } from "@/components/quotes/NegotiationMarkupCard"; diff --git a/src/components/quotes/QuoteStatusTimeline.tsx b/src/components/quotes/QuoteStatusTimeline.tsx index 7e1940831..ac2e9c77c 100644 --- a/src/components/quotes/QuoteStatusTimeline.tsx +++ b/src/components/quotes/QuoteStatusTimeline.tsx @@ -1,4 +1,4 @@ -import { Check, Clock, Eye, FileText, RefreshCw, Send, Shield, ThumbsDown, ThumbsUp, AlertTriangle } from "lucide-react"; +import { Check, Clock, FileText, RefreshCw, Send, Shield, ThumbsDown, AlertTriangle } from "lucide-react"; import { cn } from "@/lib/utils"; import { format } from "date-fns"; import { ptBR } from "date-fns/locale"; diff --git a/src/components/quotes/QuoteValidityBanner.tsx b/src/components/quotes/QuoteValidityBanner.tsx index ac38a1fe7..f7d74d921 100644 --- a/src/components/quotes/QuoteValidityBanner.tsx +++ b/src/components/quotes/QuoteValidityBanner.tsx @@ -1,6 +1,5 @@ import { differenceInDays, parseISO, isValid } from "date-fns"; import { AlertTriangle, Clock, CheckCircle2 } from "lucide-react"; -import { cn } from "@/lib/utils"; interface QuoteValidityBannerProps { validUntil?: string; diff --git a/src/components/quotes/QuoteVersionCompare.tsx b/src/components/quotes/QuoteVersionCompare.tsx index 776f2c0be..a650fbc3a 100644 --- a/src/components/quotes/QuoteVersionCompare.tsx +++ b/src/components/quotes/QuoteVersionCompare.tsx @@ -207,7 +207,7 @@ export function QuoteVersionCompare({ open, onOpenChange, versions, currentQuote {[leftDetail, rightDetail].map((detail) => (

v{detail.version}

- {detail.items.map((item, i) => { + {detail.items.map((item, _i) => { const otherDetail = detail === leftDetail ? rightDetail : leftDetail; const otherItem = otherDetail.items.find(oi => oi.product_sku === item.product_sku); const isNew = !otherItem; diff --git a/src/components/quotes/QuoteVersionHistory.tsx b/src/components/quotes/QuoteVersionHistory.tsx index a483ed086..eeb363f40 100644 --- a/src/components/quotes/QuoteVersionHistory.tsx +++ b/src/components/quotes/QuoteVersionHistory.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { format } from "date-fns"; import { ptBR } from "date-fns/locale"; -import { GitBranch, GitCompare, Plus, Eye, Check, Clock, FileText } from "lucide-react"; +import { GitBranch, GitCompare, Plus, Eye, Clock, FileText } from "lucide-react"; import { QuoteVersionCompare } from "./QuoteVersionCompare"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; @@ -10,7 +10,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { ScrollArea } from "@/components/ui/scroll-area"; -import { useQuoteVersions, type QuoteVersion } from "@/hooks/quotes"; +import { useQuoteVersions } from "@/hooks/quotes"; import { QUOTE_STATUS_CONFIG } from "@/lib/quote-status-config"; import { formatCurrency } from "@/lib/format"; diff --git a/src/components/quotes/SaveAsTemplateButton.tsx b/src/components/quotes/SaveAsTemplateButton.tsx index ac1faaa48..55ab330b4 100644 --- a/src/components/quotes/SaveAsTemplateButton.tsx +++ b/src/components/quotes/SaveAsTemplateButton.tsx @@ -38,7 +38,7 @@ export function SaveAsTemplateButton({ onSaved?.(); }; - const initialTemplate = { + const _initialTemplate = { name: "", description: "", is_default: false, diff --git a/src/components/ramo-atividade/RamoAtividadeGroupAccordion.tsx b/src/components/ramo-atividade/RamoAtividadeGroupAccordion.tsx index a98944b2e..b559ce1c6 100644 --- a/src/components/ramo-atividade/RamoAtividadeGroupAccordion.tsx +++ b/src/components/ramo-atividade/RamoAtividadeGroupAccordion.tsx @@ -1,5 +1,5 @@ -import React, { useState } from "react"; -import { ChevronDown, Building2, Check } from "lucide-react"; +import { useState } from "react"; +import { ChevronDown, Check } from "lucide-react"; import { Checkbox } from "@/components/ui/checkbox"; import { cn } from "@/lib/utils"; import type { RamoAtividadeGroup, SegmentoComplete } from "@/types/ramo-atividade"; diff --git a/src/components/reports/ScheduledReportsManager.tsx b/src/components/reports/ScheduledReportsManager.tsx index 2ebf2186c..a0c23a493 100644 --- a/src/components/reports/ScheduledReportsManager.tsx +++ b/src/components/reports/ScheduledReportsManager.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { format } from 'date-fns'; import { ptBR } from 'date-fns/locale'; -import { CalendarClock, Plus, Trash2, Pause, Play, Mail, Clock, FileBarChart } from 'lucide-react'; +import { CalendarClock, Plus, Trash2, Mail, Clock, FileBarChart } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; @@ -10,7 +10,6 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; -import { Separator } from '@/components/ui/separator'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; import { useScheduledReports, diff --git a/src/components/search/GlobalSearchHelpers.tsx b/src/components/search/GlobalSearchHelpers.tsx index 6fba2754d..32f381d47 100644 --- a/src/components/search/GlobalSearchHelpers.tsx +++ b/src/components/search/GlobalSearchHelpers.tsx @@ -6,7 +6,7 @@ import { Badge } from "@/components/ui/badge"; import { CommandItem, } from "@/components/ui/command"; -import { Trophy, Medal, ArrowUpRight, ChevronRight } from "lucide-react"; +import { Trophy, Medal, ArrowUpRight } from "lucide-react"; import { cn } from "@/lib/utils"; export const paletteItemStateClass = diff --git a/src/components/search/GlobalSearchIdleState.tsx b/src/components/search/GlobalSearchIdleState.tsx index 8a1caa91e..437dbd1e3 100644 --- a/src/components/search/GlobalSearchIdleState.tsx +++ b/src/components/search/GlobalSearchIdleState.tsx @@ -67,7 +67,7 @@ function RankBadge({ index }: { index: number }) { } /* ── Quick Actions ── */ -const quickActions = [ +const _quickActions = [ { id: "new-quote", title: "Novo Orçamento", description: "Criar um novo orçamento", icon: , href: "/orcamentos/novo", shortcut: "N", highlight: true }, { id: "products", title: "Catálogo de Produtos", description: "Ver todos os produtos", icon: , href: "/" }, ]; diff --git a/src/components/search/GlobalSearchPalette.tsx b/src/components/search/GlobalSearchPalette.tsx index 210c30155..d8e67b3b2 100644 --- a/src/components/search/GlobalSearchPalette.tsx +++ b/src/components/search/GlobalSearchPalette.tsx @@ -2,10 +2,10 @@ * GlobalSearchPalette — High-contrast black redesign * Helper components extracted to GlobalSearchHelpers.tsx */ -import React, { lazy, Suspense, useEffect, useCallback } from "react"; +import { lazy, Suspense, useEffect, useCallback } from "react"; import { - CommandDialog, CommandEmpty, CommandGroup, CommandInput, - CommandItem, CommandList, CommandSeparator, + CommandDialog, CommandGroup, CommandInput, + CommandItem, CommandList } from "@/components/ui/command"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -20,7 +20,7 @@ import { cn } from "@/lib/utils"; import { useGlobalSearch } from "./useGlobalSearch"; import { typeConfig } from "./search-types"; import { GlobalSearchIdleState } from "./GlobalSearchIdleState"; -import { paletteItemStateClass, NavCard, staggerStyle, type QuickAction } from "./GlobalSearchHelpers"; +import { paletteItemStateClass, staggerStyle, type QuickAction } from "./GlobalSearchHelpers"; import { HighlightMatch } from "./HighlightMatch"; import { EmptySearchState } from "./EmptySearchState"; diff --git a/src/components/search/VoiceSearchOverlay.tsx b/src/components/search/VoiceSearchOverlay.tsx index e761e3151..3b8c74316 100644 --- a/src/components/search/VoiceSearchOverlay.tsx +++ b/src/components/search/VoiceSearchOverlay.tsx @@ -46,7 +46,7 @@ export const VoiceSearchOverlay = React.forwardRef `/placeholder.svg`; +const img = (_name: string) => `/placeholder.svg`; export const MOCK_MATCH_PRODUCTS: Product[] = [ // ── CHURRASCO cluster ── diff --git a/src/hooks/auth/useProfileRoles.ts b/src/hooks/auth/useProfileRoles.ts index 34d641565..99f3cd88f 100644 --- a/src/hooks/auth/useProfileRoles.ts +++ b/src/hooks/auth/useProfileRoles.ts @@ -3,7 +3,6 @@ import { supabase } from '@/integrations/supabase/client'; import { authService } from '@/services/authService'; import { authDebug, authDebugError } from '@/lib/auth/auth-debug'; import { type AppRole, type Profile } from '@/contexts/AuthContext'; -import { logger } from '@/lib/logger'; export function useProfileRoles() { const [profile, setProfile] = useState(null); diff --git a/src/hooks/products/useStockAlerts.integration.test.tsx b/src/hooks/products/useStockAlerts.integration.test.tsx index 915827c0f..be92ca2ee 100644 --- a/src/hooks/products/useStockAlerts.integration.test.tsx +++ b/src/hooks/products/useStockAlerts.integration.test.tsx @@ -55,7 +55,7 @@ describe('useStockAlerts integration', () => { await waitFor(() => expect(result.current.isSuccess).toBe(true)); - const callArgs = invokeExternalDbMock.mock.calls[0][0]; + const callArgs = invokeExternalDbMock.mock.calls[0][0] as { select: string }; const selectStr = callArgs.select; const fields = selectStr.split(',').map((f: string) => f.trim()); diff --git a/src/hooks/simulator/useSimulatorWizard.ts b/src/hooks/simulator/useSimulatorWizard.ts index a18c963d9..7366fa983 100644 --- a/src/hooks/simulator/useSimulatorWizard.ts +++ b/src/hooks/simulator/useSimulatorWizard.ts @@ -52,7 +52,7 @@ export function useSimulatorWizard() { staleTime: 10 * 60 * 1000, }); - useEffect(() => { if (locationsData) dispatch({ type: 'SET_AVAILABLE_LOCATIONS', payload: locationsData }); }, [locationsData]); + useEffect(() => { if (locationsData) dispatch({ type: 'SET_AVAILABLE_LOCATIONS', payload: locationsData }); }, [locationsData, dispatch]); const { fetchComparisonPrices, confirmTechnique } = useWizardPricing({ state, dispatch }); useWizardPersistence(state); @@ -61,39 +61,39 @@ export function useSimulatorWizard() { const setStep = useCallback((step: WizardStep) => { if (canNavigateToStep(step, state)) dispatch({ type: 'SET_STEP', payload: step }); else toast.warning('Complete os passos anteriores primeiro'); - }, [state]); + }, [state, dispatch]); const nextStep = useCallback(() => { const next = getNextStep(state.currentStep); if (next && isStepComplete(state.currentStep, state)) dispatch({ type: 'SET_STEP', payload: next }); - }, [state]); + }, [state, dispatch]); const previousStep = useCallback(() => { const prev = getPreviousStep(state.currentStep); if (prev) dispatch({ type: 'SET_STEP', payload: prev }); - }, [state.currentStep]); + }, [state.currentStep, dispatch]); const selectProduct = useCallback((product: SelectedProduct | null) => { dispatch({ type: 'SELECT_PRODUCT', payload: product }); if (product) dispatch({ type: 'SET_STEP', payload: 'location' }); - }, []); + }, [dispatch]); const setQuantity = useCallback((quantity: number) => { const newQty = Math.max(1, quantity); dispatch({ type: 'SET_QUANTITY', payload: newQty }); if (state.personalizations.length > 0 && newQty !== state.quantity) toast.info('Recalculando preços para nova tiragem...', { duration: 2000 }); - }, [state.personalizations.length, state.quantity]); + }, [state.personalizations.length, state.quantity, dispatch]); const selectLocation = useCallback((location: EngravingLocation | null) => { dispatch({ type: 'SELECT_LOCATION', payload: location }); if (location) dispatch({ type: 'SET_STEP', payload: 'specs' }); - }, []); + }, [dispatch]); - const updateSpecs = useCallback((specs: Partial) => { dispatch({ type: 'UPDATE_SPECS', payload: specs }); }, []); - const removePersonalization = useCallback((id: string) => { dispatch({ type: 'REMOVE_PERSONALIZATION', payload: id }); toast.info('Gravação removida'); }, []); - const removeAllPersonalizations = useCallback(() => { dispatch({ type: 'REMOVE_ALL_PERSONALIZATIONS' }); toast.info('Todas as gravações removidas'); }, []); - const editPersonalization = useCallback((index: number) => { dispatch({ type: 'EDIT_PERSONALIZATION', payload: index }); }, []); + const updateSpecs = useCallback((specs: Partial) => { dispatch({ type: 'UPDATE_SPECS', payload: specs }); }, [dispatch]); + const removePersonalization = useCallback((id: string) => { dispatch({ type: 'REMOVE_PERSONALIZATION', payload: id }); toast.info('Gravação removida'); }, [dispatch]); + const removeAllPersonalizations = useCallback(() => { dispatch({ type: 'REMOVE_ALL_PERSONALIZATIONS' }); toast.info('Todas as gravações removidas'); }, [dispatch]); + const editPersonalization = useCallback((index: number) => { dispatch({ type: 'EDIT_PERSONALIZATION', payload: index }); }, [dispatch]); const startNewPersonalization = useCallback(() => { const usedIds = new Set(state.personalizations.map(p => p.location.id)); @@ -101,9 +101,9 @@ export function useSimulatorWizard() { toast.warning('Todos os locais já foram personalizados'); return; } dispatch({ type: 'START_NEW_PERSONALIZATION' }); - }, [state.personalizations, state.availableLocations]); + }, [state.personalizations, state.availableLocations, dispatch]); - const cancelPersonalization = useCallback(() => { dispatch({ type: 'CANCEL_PERSONALIZATION' }); }, []); + const cancelPersonalization = useCallback(() => { dispatch({ type: 'CANCEL_PERSONALIZATION' }); }, [dispatch]); const duplicatePersonalization = useCallback((sourceId: string, targetLocationId: string) => { const targetLocation = state.availableLocations.find(loc => loc.id === targetLocationId); @@ -113,9 +113,9 @@ export function useSimulatorWizard() { } dispatch({ type: 'DUPLICATE_PERSONALIZATION', payload: { sourceId, targetLocation } }); toast.success(`Personalização duplicada para ${targetLocation.locationName}`); - }, [state.availableLocations, state.personalizations]); + }, [state.availableLocations, state.personalizations, dispatch]); - const resetWizard = useCallback(() => { dispatch({ type: 'RESET_WIZARD' }); clearSession(); }, []); + const resetWizard = useCallback(() => { dispatch({ type: 'RESET_WIZARD' }); clearSession(); }, [dispatch]); // Computed const effectivePrice = useMemo(() => state.selectedProduct?.price || 0, [state.selectedProduct]); diff --git a/src/hooks/ui/useGlobalShortcuts.ts b/src/hooks/ui/useGlobalShortcuts.ts index 5cf07652f..dcb9bdcc2 100644 --- a/src/hooks/ui/useGlobalShortcuts.ts +++ b/src/hooks/ui/useGlobalShortcuts.ts @@ -33,7 +33,7 @@ export function useGlobalShortcuts(handlers?: ShortcutHandlers) { let onboarding: any = null; try { onboarding = useOnboardingContext(); - } catch (e) { + } catch (_e) { // Context may not be available outside MainLayout } diff --git a/src/integrations/supabase/client.ts b/src/integrations/supabase/client.ts index dfa79a455..dbaa41dd8 100644 --- a/src/integrations/supabase/client.ts +++ b/src/integrations/supabase/client.ts @@ -14,10 +14,26 @@ if (!SUPABASE_URL || !SUPABASE_PUBLISHABLE_KEY) { // Import the supabase client like this: // import { supabase } from "@/integrations/supabase/client"; +type SupabaseStorage = { + getItem: Storage['getItem']; + setItem: Storage['setItem']; + removeItem: Storage['removeItem']; +}; + +const getStorageOrUndefined = (): SupabaseStorage | undefined => { + if (typeof window === 'undefined' || !window.localStorage) { + return undefined; + } + + return window.localStorage; +}; + +const storage = getStorageOrUndefined(); + export const supabase = createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, { auth: { - storage: localStorage, - persistSession: true, + storage, + persistSession: Boolean(storage), autoRefreshToken: true, } }); diff --git a/src/lib/cloud-status.ts b/src/lib/cloud-status.ts index 102809c9b..79aedbc2c 100644 --- a/src/lib/cloud-status.ts +++ b/src/lib/cloud-status.ts @@ -38,7 +38,7 @@ const PROBE_TIMEOUT_MS = 5000; // Increased from 2.5s to 5s to avoid false posit let cached: CloudStatusSnapshot | null = null; let inFlight: Promise | null = null; -let consecutiveFailures = 0; +let consecutiveFailures = 0; export interface StatusHistoryEntry { status: CloudStatus; @@ -61,9 +61,16 @@ function getStatusHistory(): StatusHistoryEntry[] { } function saveStatusHistory(entry: StatusHistoryEntry) { - const history = getStatusHistory(); - history.push(entry); - localStorage.setItem(HISTORY_KEY, JSON.stringify(history.slice(-100))); // Keep last 100 + try { + const history = getStatusHistory(); + history.push(entry); + localStorage.setItem(HISTORY_KEY, JSON.stringify(history.slice(-100))); // Keep last 100 + } catch (error) { + logger.warn('[CloudStatus] failed to persist status history', { + HISTORY_KEY, + error: error instanceof Error ? error.message : String(error), + }); + } } export function getStatusTimeline() { @@ -72,11 +79,16 @@ export function getStatusTimeline() { const FAILURE_THRESHOLD = 2; // Need 2 consecutive full failures to go 'down' -const target: EventTarget = typeof EventTarget !== 'undefined' ? new EventTarget() : ({ - addEventListener() {}, - removeEventListener() {}, - dispatchEvent() { return true; }, -} as unknown as EventTarget); +const target: EventTarget = + typeof EventTarget !== 'undefined' + ? new EventTarget() + : ({ + addEventListener() {}, + removeEventListener() {}, + dispatchEvent() { + return true; + }, + } as unknown as EventTarget); const EVENT_NAME = 'cloud-status-change'; @@ -94,8 +106,14 @@ function withTimeout(p: Promise, ms: number): Promise { return new Promise((resolve, reject) => { const t = setTimeout(() => reject(new Error(`timeout ${ms}ms`)), ms); p.then( - (v) => { clearTimeout(t); resolve(v); }, - (e) => { clearTimeout(t); reject(e); }, + (v) => { + clearTimeout(t); + resolve(v); + }, + (e) => { + clearTimeout(t); + reject(e); + }, ); }); } @@ -125,7 +143,10 @@ async function checkRest(): Promise<{ ok: boolean; ms: number }> { PROBE_TIMEOUT_MS, ); // PostgREST typically returns 200/404 for HEAD / - return { ok: res.ok || res.status === 404 || res.status === 401, ms: Math.round(performance.now() - t0) }; + return { + ok: res.ok || res.status === 404 || res.status === 401, + ms: Math.round(performance.now() - t0), + }; } catch { return { ok: false, ms: Math.round(performance.now() - t0) }; } @@ -134,17 +155,17 @@ async function checkRest(): Promise<{ ok: boolean; ms: number }> { function deriveStatus(signals: CloudStatusSnapshot['signals']): CloudStatus { const okSignals = [signals.auth.ok, signals.bridge.ok, signals.rest.ok]; const okCount = okSignals.filter(Boolean).length; - + if (okCount === 3) { consecutiveFailures = 0; return 'healthy'; } - + if (okCount === 2) { consecutiveFailures = 0; return 'warming'; } - + if (okCount === 1) { consecutiveFailures = 0; return 'degraded'; @@ -152,12 +173,12 @@ function deriveStatus(signals: CloudStatusSnapshot['signals']): CloudStatus { // okCount === 0: Full failure detected consecutiveFailures++; - + // If we don't have enough consecutive failures yet, return 'degraded' instead of 'down' if (consecutiveFailures < FAILURE_THRESHOLD) { return 'degraded'; } - + return 'down'; } @@ -183,17 +204,20 @@ export async function probeCloudStatus(force = false): Promise void, -): () => void { +export function onCloudStatusChange(cb: (snapshot: CloudStatusSnapshot) => void): () => void { const handler = (e: Event) => cb((e as CustomEvent).detail); target.addEventListener(EVENT_NAME, handler); return () => target.removeEventListener(EVENT_NAME, handler); @@ -237,8 +259,7 @@ export async function ensureCloudReady( let attempt = 0; let snap = await probeCloudStatus(false); - const isReady = (s: CloudStatus) => - s === 'healthy' || (acceptWarming && s === 'warming'); + const isReady = (s: CloudStatus) => s === 'healthy' || (acceptWarming && s === 'warming'); while (!isReady(snap.status) && performance.now() - start < totalTimeoutMs) { attempt++; diff --git a/src/lib/lazyWithRetry.ts b/src/lib/lazyWithRetry.ts index ca4c6e9d2..1b627b683 100644 --- a/src/lib/lazyWithRetry.ts +++ b/src/lib/lazyWithRetry.ts @@ -1,16 +1,16 @@ import { lazy, type ComponentType, createElement } from 'react'; -import { logger } from "@/lib/logger"; -import { attemptChunkRecovery, isChunkLoadError, extractChunkUrl } from "@/lib/chunk-recovery"; -import { getFallback } from "@/components/layout/SkeletonLoaders"; +import { logger } from '@/lib/logger'; +import { attemptChunkRecovery, isChunkLoadError } from '@/lib/chunk-recovery'; +import { getFallback } from '@/components/layout/SkeletonLoaders'; /** * Wrapper around React.lazy that retries on chunk loading failures. * Handles stale cache issues after deployments and Vite 502 spikes. */ -export function lazyWithRetry>( +export function lazyWithRetry>( componentImport: () => Promise<{ default: T }>, retries = 3, - interval = 1000 + interval = 1000, ): React.LazyExoticComponent { return lazy(async () => { let lastError: Error | undefined; @@ -23,7 +23,7 @@ export function lazyWithRetry>( if (isChunkLoadError(error)) { logger.warn(`Chunk load failed (attempt ${i + 1}/${retries}), retrying...`); - await new Promise(resolve => setTimeout(resolve, interval * (i + 1))); + await new Promise((resolve) => setTimeout(resolve, interval * (i + 1))); // Última tentativa: aciona recovery agressivo (hard reload + cache bust). if (i === retries - 1) { @@ -31,13 +31,17 @@ export function lazyWithRetry>( if (reloaded) { // Retorna um componente placeholder que renderiza o esqueleto apropriado // enquanto o hard-reload acontece no fundo. - return { + return { default: (() => { const url = typeof window !== 'undefined' ? window.location.pathname : '/'; - return createElement('div', { - className: "animate-pulse" - }, getFallback(url)); - }) as unknown as T + return createElement( + 'div', + { + className: 'animate-pulse', + }, + getFallback(url), + ); + }) as unknown as T, }; } throw error; @@ -51,4 +55,3 @@ export function lazyWithRetry>( throw lastError; }); } - diff --git a/src/lib/telemetry/instrumentationControl.test.ts b/src/lib/telemetry/instrumentationControl.test.ts new file mode 100644 index 000000000..9171b30bf --- /dev/null +++ b/src/lib/telemetry/instrumentationControl.test.ts @@ -0,0 +1,75 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const STORAGE_KEY = 'lov:instrumentation:paused'; + +type MockStorage = { + getItem: ReturnType; + setItem: ReturnType; + removeItem: ReturnType; +}; + +function installLocalStorage(overrides?: Partial): MockStorage { + const storage: MockStorage = { + getItem: vi.fn(() => null), + setItem: vi.fn(), + removeItem: vi.fn(), + ...overrides, + }; + + Object.defineProperty(globalThis, 'localStorage', { + configurable: true, + value: storage, + }); + + return storage; +} + +async function loadModule() { + vi.resetModules(); + return import('./instrumentationControl'); +} + +describe('instrumentationControl storage contract', () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it('assume pausado quando valor estiver ausente e persiste "1"', async () => { + const storage = installLocalStorage({ getItem: vi.fn(() => null) }); + const mod = await loadModule(); + + expect(mod.isInstrumentationPaused()).toBe(true); + expect(storage.setItem).toHaveBeenCalledWith(STORAGE_KEY, '1'); + }); + + it('interpreta "0" como ativo', async () => { + const storage = installLocalStorage({ getItem: vi.fn(() => '0') }); + const mod = await loadModule(); + + expect(mod.isInstrumentationPaused()).toBe(false); + expect(storage.setItem).not.toHaveBeenCalled(); + }); + + it('interpreta "1" como pausado', async () => { + const storage = installLocalStorage({ getItem: vi.fn(() => '1') }); + const mod = await loadModule(); + + expect(mod.isInstrumentationPaused()).toBe(true); + expect(storage.setItem).not.toHaveBeenCalled(); + }); + + it('tolera exceções de storage (leitura e escrita)', async () => { + installLocalStorage({ + getItem: vi.fn(() => { + throw new Error('read failed'); + }), + setItem: vi.fn(() => { + throw new Error('write failed'); + }), + }); + + const mod = await loadModule(); + expect(mod.isInstrumentationPaused()).toBe(true); + expect(() => mod.setInstrumentationPaused(false)).not.toThrow(); + }); +}); diff --git a/src/lib/telemetry/instrumentationControl.ts b/src/lib/telemetry/instrumentationControl.ts index c3ba7013c..3fb5f90e8 100644 --- a/src/lib/telemetry/instrumentationControl.ts +++ b/src/lib/telemetry/instrumentationControl.ts @@ -10,8 +10,10 @@ * Permite verificar, sem recarregar a página, se a instrumentação é a * responsável por lentidão percebida durante a navegação. * - * Persistência: opt-in via localStorage para sobreviver a F5 quando o - * desenvolvedor decide rodar com tudo desligado. + * Persistência via localStorage para sobreviver a F5. + * Contrato explícito: + * - '1' => pausado + * - '0' => ativo */ const STORAGE_KEY = 'lov:instrumentation:paused'; @@ -21,9 +23,14 @@ let paused = (() => { if (typeof localStorage === 'undefined') return true; const v = localStorage.getItem(STORAGE_KEY); // Kill-switch FORÇADO: default = pausado. Para reativar, set '0' explicitamente. + // Valores ausentes/inválidos convergem para '1' (pausado). if (v === '0') return false; if (v !== '1') { - try { localStorage.setItem(STORAGE_KEY, '1'); } catch { /* noop */ } + try { + localStorage.setItem(STORAGE_KEY, '1'); + } catch { + /* noop */ + } } return true; } catch { @@ -41,12 +48,17 @@ export function setInstrumentationPaused(next: boolean): void { if (paused === next) return; paused = next; try { - if (next) localStorage.setItem(STORAGE_KEY, '1'); - else localStorage.removeItem(STORAGE_KEY); - } catch { /* noop */ } + localStorage.setItem(STORAGE_KEY, next ? '1' : '0'); + } catch { + /* noop */ + } // Notifica para que watchdog reaja (start/stop) e UI re-renderize. for (const l of listeners) { - try { l(); } catch { /* noop */ } + try { + l(); + } catch { + /* noop */ + } } } @@ -57,5 +69,7 @@ export function toggleInstrumentationPaused(): boolean { export function subscribeInstrumentationPaused(fn: () => void): () => void { listeners.add(fn); - return () => { listeners.delete(fn); }; + return () => { + listeners.delete(fn); + }; } diff --git a/src/pages/Simulation.tsx b/src/pages/Simulation.tsx index 23bb979a9..bbbad63b8 100644 --- a/src/pages/Simulation.tsx +++ b/src/pages/Simulation.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { Play, CheckCircle2, XCircle, Loader2, Database, Globe, Zap, ShieldAlert, BarChart3, AlertTriangle } from "lucide-react"; +import { Play, CheckCircle2, Loader2, Database, Zap, ShieldAlert, BarChart3, AlertTriangle } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; diff --git a/src/pages/auth/AuthBranding.tsx b/src/pages/auth/AuthBranding.tsx index 9706de120..230af424f 100644 --- a/src/pages/auth/AuthBranding.tsx +++ b/src/pages/auth/AuthBranding.tsx @@ -2,8 +2,8 @@ * Left-side branding panel for Auth page — extracted for modularity */ import React, { useState, useEffect, useRef, useCallback } from "react"; -import { motion, AnimatePresence } from "framer-motion"; -import { Gift, Package, Factory, SlidersHorizontal, Brain, Rocket } from "lucide-react"; +import { motion } from "framer-motion"; +import { Package, Factory, SlidersHorizontal, Brain, Rocket } from "lucide-react"; import { AppLogo } from "@/components/layout/AppLogo"; import astronautSvg from "@/assets/astronaut.svg"; @@ -250,7 +250,7 @@ export const SpaceScene = React.memo(({ isFull = true }: { isFull?: boolean }) = ))} {/* Floating Astronauts — Sincronizados, Menores e com Parallax Mouse + Scroll */} - {!config.reducedMotion && astronauts.map((a, idx) => { + {!config.reducedMotion && astronauts.map((a, _idx) => { // Tamanhos reduzidos e escala baseada na profundidade, perfil global e ajuste individual const baseSize = 35; const size = baseSize * a.depth * config.depthProfile * (a.individualScale ?? 1.0); diff --git a/src/pages/auth/ResetPassword.tsx b/src/pages/auth/ResetPassword.tsx index 60c6374db..7723b3ebd 100644 --- a/src/pages/auth/ResetPassword.tsx +++ b/src/pages/auth/ResetPassword.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { z } from 'zod'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import { Lock, Loader2, Eye, EyeOff, CheckCircle, Sparkles, Rocket } from 'lucide-react'; +import { Lock, Loader2, Eye, EyeOff, CheckCircle } from 'lucide-react'; import { AppLogo } from '@/components/layout/AppLogo'; import { PasswordStrengthIndicator } from '@/components/auth/PasswordStrengthIndicator'; import { Button } from '@/components/ui/button'; @@ -15,7 +15,6 @@ import { supabase } from '@/integrations/supabase/client'; import { PageSEO } from '@/components/seo/PageSEO'; import { LegalFooter } from '@/components/auth/LegalFooter'; import { SpaceScene } from "@/pages/auth/AuthBranding"; -import { motion, AnimatePresence } from 'framer-motion'; const resetPasswordSchema = z .object({ diff --git a/src/pages/auth/SSOCallbackPage.tsx b/src/pages/auth/SSOCallbackPage.tsx index 4e0867bdf..5654f8548 100644 --- a/src/pages/auth/SSOCallbackPage.tsx +++ b/src/pages/auth/SSOCallbackPage.tsx @@ -11,7 +11,6 @@ import { consumePostLoginRedirect } from '@/lib/auth/post-login-redirect'; import { clearOAuthPending } from '@/lib/auth/oauth-pending'; import { explainOAuthError, type OAuthErrorExplanation } from '@/lib/auth/oauth-error-explainer'; import { SpaceScene } from "@/pages/auth/AuthBranding"; -import { motion } from 'framer-motion'; /** * Callback do login social via Supabase Auth. diff --git a/src/pages/products/ComparePage.tsx b/src/pages/products/ComparePage.tsx index 991f7318c..e3b0f2a59 100644 --- a/src/pages/products/ComparePage.tsx +++ b/src/pages/products/ComparePage.tsx @@ -10,7 +10,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { PageSEO } from '@/components/seo/PageSEO'; import { useComparisonStore, type CompareVariantInfo } from '@/stores/useComparisonStore'; -import type { Product, ProductColor } from '@/types/product'; +import type { Product, ProductColor } from '@/types/product-catalog'; import { useProductsContext } from '@/contexts/ProductsContext'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; @@ -29,7 +29,7 @@ import { } from 'lucide-react'; import { cn } from '@/lib/utils'; import { SyncedZoomGallery } from '@/components/compare/SyncedZoomGallery'; -import { CompareTableView, type CompareEntry } from '@/components/compare/CompareTableView'; +import { CompareTableView } from '@/components/compare/CompareTableView'; import { ComparisonScoreCard } from '@/components/compare/ComparisonScoreCard'; import { ComparisonRadarChart } from '@/components/compare/ComparisonRadarChart'; import { AIComparisonAdvisor } from '@/components/compare/AIComparisonAdvisor'; @@ -372,15 +372,9 @@ export default function ComparePage() { - {/* Os produtos vêm de getProductsByIds → mapPromobrindToProduct, - cujo shape de runtime é o de @/types/product-catalog (camelCase). - A tipagem local desta página os declara como @/types/product; - a asserção abaixo reconcilia esse descompasso pré-existente no - único ponto de contato com o CompareTableView (já migrado ao - tipo correto). Ver follow-up de unificação dos tipos de Product. */} ) - .eq('id', quote.id); + .update({ status: 'pending' } as never) + .eq('id', quote.id ?? ''); await logQuoteHistory( - quote.id, + quote.id ?? '', 'status_change', 'Status revertido para Pendente', { oldValue: 'sent', newValue: 'pending' }, @@ -233,7 +233,7 @@ export default function QuoteViewPage() { { - const newQuote = await duplicateQuote(quote.id); + const newQuote = await duplicateQuote(quote.id ?? ''); if (newQuote?.id) navigate(`/orcamentos/${newQuote.id}`); }} > @@ -253,7 +253,7 @@ export default function QuoteViewPage() { Histórico de Alterações
- +
@@ -313,7 +313,7 @@ export default function QuoteViewPage() { clientCnpj={clientCnpj} /> - + ({ + useLocation: () => ({ pathname: '/rota-sem-prefetch' }), +})); + +describe('RoutePrefetcher', () => { + const originalNavigatorDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'navigator'); + + afterEach(() => { + if (originalNavigatorDescriptor) { + Object.defineProperty(globalThis, 'navigator', originalNavigatorDescriptor); + } + }); + + it('does not throw when navigator is unavailable', () => { + Object.defineProperty(globalThis, 'navigator', { + configurable: true, + value: undefined, + }); + + expect(() => render()).not.toThrow(); + }); +}); diff --git a/src/routes/RoutePrefetcher.tsx b/src/routes/RoutePrefetcher.tsx index ed419e7c9..8c9a5dfc1 100644 --- a/src/routes/RoutePrefetcher.tsx +++ b/src/routes/RoutePrefetcher.tsx @@ -1,5 +1,5 @@ -import { useEffect } from "react"; -import { useLocation } from "react-router-dom"; +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; /** * 🚀 PREFETCH CORE CHUNKS: Warm up the next predicted routes for instant feel. @@ -11,31 +11,44 @@ export function RoutePrefetcher() { const { pathname } = useLocation(); useEffect(() => { + type ConnectionInfo = { + saveData?: boolean; + effectiveType?: string; + }; + + type NavigatorWithConnection = Navigator & { + connection?: ConnectionInfo; + }; + // Only prefetch if we're not on a mobile connection or low power mode - const connection = (navigator as any).connection; + const connection = + typeof window !== 'undefined' && typeof navigator !== 'undefined' + ? (navigator as NavigatorWithConnection).connection + : undefined; + if (connection && (connection.saveData || connection.effectiveType === '2g')) { return; } - if (pathname === "/auth" || pathname === "/login") { + if (pathname === '/auth' || pathname === '/login') { // Prefetch dashboard early - import("@/pages/Index"); - import("@/pages/products/FiltersPage"); - } else if (pathname === "/") { + import('@/pages/Index'); + import('@/pages/products/FiltersPage'); + } else if (pathname === '/') { // Prefetch heavy pages from dashboard + Auth (sessão pode expirar) - import("@/pages/products/FiltersPage"); - import("@/pages/quotes/QuotesListPage"); - import("@/pages/clients/ClientsPage"); - import("@/pages/auth/Auth"); - } else if (pathname === "/produtos") { - import("@/pages/products/ProductDetail"); - import("@/pages/tools/PriceSimulatorPage"); + import('@/pages/products/FiltersPage'); + import('@/pages/quotes/QuotesListPage'); + import('@/pages/clients/ClientsPage'); + import('@/pages/auth/Auth'); + } else if (pathname === '/produtos') { + import('@/pages/products/ProductDetail'); + import('@/pages/tools/PriceSimulatorPage'); } // Secondary priority prefetch const timeoutId = setTimeout(() => { - if (pathname !== "/orcamentos/novo") import("@/pages/quotes/QuoteBuilderPage"); - if (pathname === "/produtos") import("@/pages/mockups/MockupGenerator"); + if (pathname !== '/orcamentos/novo') import('@/pages/quotes/QuoteBuilderPage'); + if (pathname === '/produtos') import('@/pages/mockups/MockupGenerator'); }, 2500); return () => clearTimeout(timeoutId); diff --git a/src/services/authService.ts b/src/services/authService.ts index 565efce20..6f9f1ea2a 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -1,6 +1,4 @@ import { supabase } from "@/integrations/supabase/client"; -import { AppRole, Profile } from "@/contexts/AuthContext"; -import { authDebug, authDebugError } from "@/lib/auth/auth-debug"; import { logger } from "@/lib/logger"; export const authService = { diff --git a/src/tests/AdminMobileInteraction.test.tsx b/src/tests/AdminMobileInteraction.test.tsx index 02428ddc2..a6960ac88 100644 --- a/src/tests/AdminMobileInteraction.test.tsx +++ b/src/tests/AdminMobileInteraction.test.tsx @@ -1,4 +1,4 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; diff --git a/src/tests/AdminStructuralComparison.test.tsx b/src/tests/AdminStructuralComparison.test.tsx index 46d8177e6..a574cff13 100644 --- a/src/tests/AdminStructuralComparison.test.tsx +++ b/src/tests/AdminStructuralComparison.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { render, screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ThemeProvider } from '@/contexts/ThemeContext'; diff --git a/src/tests/CatalogFilteringLogic.test.tsx b/src/tests/CatalogFilteringLogic.test.tsx index bd0bc96b0..b9df047c5 100644 --- a/src/tests/CatalogFilteringLogic.test.tsx +++ b/src/tests/CatalogFilteringLogic.test.tsx @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { renderHook } from '@testing-library/react'; import { useCatalogFiltering } from '../hooks/products/useCatalogFiltering'; import { defaultFilters } from '../components/filters/FilterPanel'; diff --git a/src/tests/ScenarioSimulation.test.ts b/src/tests/ScenarioSimulation.test.ts index 0a391631e..a6a124e6a 100644 --- a/src/tests/ScenarioSimulation.test.ts +++ b/src/tests/ScenarioSimulation.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { loginSchema, signupSchema } from '../lib/validations/authSchema'; +import { loginSchema } from '../lib/validations/authSchema'; import { quoteFormSchema } from '../lib/validations/quoteSchema'; describe('Real-World Scenario: Security & Validation Layer', () => { diff --git a/src/tests/quotePersistence.test.ts b/src/tests/quotePersistence.test.ts index 117eb0224..2e3de19ad 100644 --- a/src/tests/quotePersistence.test.ts +++ b/src/tests/quotePersistence.test.ts @@ -1,6 +1,5 @@ -import { describe, it, expect, vi } from 'vitest'; -import { calculateQuoteTotals, round2 } from '../hooks/quotes/quoteHelpers'; -import { quoteService } from '../services/quoteService'; +import { describe, it, expect } from 'vitest'; +import { calculateQuoteTotals } from '../hooks/quotes/quoteHelpers'; import { type QuoteItem } from '../hooks/quotes/quoteTypes'; // Mock Supabase to avoid real network calls if needed, diff --git a/src/types/jspdf-autotable.d.ts b/src/types/jspdf-autotable.d.ts index 039ed961c..46dede1ac 100644 --- a/src/types/jspdf-autotable.d.ts +++ b/src/types/jspdf-autotable.d.ts @@ -2,7 +2,7 @@ * Type declarations for jspdf-autotable plugin. * Eliminates doc.lastAutoTable pattern. */ -import { jsPDF } from "jspdf"; +import "jspdf"; declare module "jspdf" { interface jsPDF { diff --git a/src/utils/excelExport.ts b/src/utils/excelExport.ts index 7b8456e57..34e68eb39 100644 --- a/src/utils/excelExport.ts +++ b/src/utils/excelExport.ts @@ -169,7 +169,6 @@ function formatValue(value: unknown): string | number { /** * Formata moeda brasileira */ -import { formatCurrency } from "@/lib/format"; /** diff --git a/src/utils/performance.ts b/src/utils/performance.ts index ab795f216..918aba3ab 100644 --- a/src/utils/performance.ts +++ b/src/utils/performance.ts @@ -39,7 +39,7 @@ class PerformanceTracker { private saveHistory() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(this.history.slice(-MAX_HISTORY))); - } catch (e) { + } catch (_e) { // Ignore quota errors } } @@ -76,7 +76,7 @@ class PerformanceTracker { } return measure; - } catch (e) { + } catch (_e) { // Mark might not exist yet } } diff --git a/supabase/functions/_shared/contracts/schemas/product-webhook.test.ts b/supabase/functions/_shared/contracts/schemas/product-webhook.test.ts new file mode 100644 index 000000000..052f58fcd --- /dev/null +++ b/supabase/functions/_shared/contracts/schemas/product-webhook.test.ts @@ -0,0 +1,66 @@ +import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts"; +import { ProductWebhookV1 } from "./product-webhook.ts"; + +Deno.test("ProductWebhookV1 rejects variation sem shape mínima", () => { + const payload = { + action: "upsert", + product: { + sku: "SKU-1", + name: "Produto", + price: 10, + variations: ["sem-objeto"], + }, + }; + + const result = ProductWebhookV1.safeParse(payload); + assertEquals(result.success, false); +}); + +Deno.test("ProductWebhookV1 rejects variation sem identificador", () => { + const payload = { + action: "upsert", + product: { + sku: "SKU-1", + name: "Produto", + price: 10, + variations: [{ color: "red" }], + }, + }; + + const result = ProductWebhookV1.safeParse(payload); + assertEquals(result.success, false); +}); + +Deno.test("ProductWebhookV1 rejects metadata com payload massivo", () => { + const metadata = Object.fromEntries(Array.from({ length: 101 }, (_, i) => [`k${i}`, i])); + + const payload = { + action: "upsert", + product: { + sku: "SKU-1", + name: "Produto", + price: 10, + metadata, + }, + }; + + const result = ProductWebhookV1.safeParse(payload); + assertEquals(result.success, false); +}); + +Deno.test("ProductWebhookV1 rejects metadata com array gigante", () => { + const payload = { + action: "upsert", + product: { + sku: "SKU-1", + name: "Produto", + price: 10, + metadata: { + huge: Array.from({ length: 101 }, (_, i) => i), + }, + }, + }; + + const result = ProductWebhookV1.safeParse(payload); + assertEquals(result.success, false); +}); diff --git a/supabase/functions/_shared/contracts/schemas/product-webhook.ts b/supabase/functions/_shared/contracts/schemas/product-webhook.ts index 78b4ae10b..8922b663e 100644 --- a/supabase/functions/_shared/contracts/schemas/product-webhook.ts +++ b/supabase/functions/_shared/contracts/schemas/product-webhook.ts @@ -13,6 +13,46 @@ import { z } from "https://esm.sh/zod@3.23.8"; +const JsonPrimitive = z.union([z.string(), z.number(), z.boolean(), z.null()]); +type JsonValue = z.infer | { [key: string]: JsonValue } | JsonValue[]; + +const JsonValueSchema: z.ZodType = z.lazy(() => + z.union([ + JsonPrimitive, + z.array(JsonValueSchema).max(100), + z.record(z.string().max(100), JsonValueSchema).superRefine((obj, ctx) => { + if (Object.keys(obj).length > 100) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Object must have at most 100 keys", + }); + } + }), + ]) +); + +const VariationSchema = z.unknown().superRefine((value, ctx) => { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Variation must be an object" }); + return; + } + const record = value as Record; + const keys = Object.keys(record); + if (keys.length === 0) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Variation must not be empty" }); + } + if (keys.length > 30) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Variation has too many keys" }); + } + const candidateId = record.id ?? record.external_id ?? record.sku; + if (typeof candidateId !== "string" || candidateId.trim().length === 0 || candidateId.length > 255) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Variation must include id/external_id/sku as non-empty string up to 255 chars", + }); + } +}); + // --------------------------------------------------------------------------- // v1 (compatível com produção) // --------------------------------------------------------------------------- @@ -55,8 +95,23 @@ const ProductV1 = z.object({ ) .max(50) .optional(), - variations: z.array(z.any()).max(200).optional(), - metadata: z.record(z.any()).optional(), + variations: z.array(VariationSchema).max(200).optional(), + metadata: z.unknown().superRefine((value, ctx) => { + if (value === undefined) return; + const parsed = z.record(z.string().max(100), JsonValueSchema).safeParse(value); + if (!parsed.success) { + for (const issue of parsed.error.issues) { + ctx.addIssue(issue); + } + return; + } + if (Object.keys(parsed.data).length > 100) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "metadata must have at most 100 keys", + }); + } + }).optional(), }); export const ProductWebhookV1 = z.object({ diff --git a/supabase/functions/product-webhook/index.ts b/supabase/functions/product-webhook/index.ts index 707a8debf..cb599c864 100644 --- a/supabase/functions/product-webhook/index.ts +++ b/supabase/functions/product-webhook/index.ts @@ -1,4 +1,5 @@ import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4"; +import type { SupabaseClient } from "https://esm.sh/@supabase/supabase-js@2.49.4"; import { buildPublicCorsHeaders } from "../_shared/cors.ts"; import { parseContract, @@ -8,6 +9,7 @@ import { type ProductWebhookV1Payload, type ProductWebhookV2Payload, } from "../_shared/contracts/schemas/product-webhook.ts"; +import type { Database } from "../../src/integrations/supabase/types.ts"; const corsHeaders = buildPublicCorsHeaders({ extraAllowHeaders: ["x-webhook-secret", "accept-version"] }); @@ -25,7 +27,7 @@ Deno.serve(async (req) => { return new Response(null, { headers: corsHeaders }); } - const supabase = createClient(supabaseUrl, supabaseServiceKey); + const supabase = createClient(supabaseUrl, supabaseServiceKey); try { // Webhook secret check (mantido idêntico) @@ -148,7 +150,7 @@ Deno.serve(async (req) => { }); async function upsertProducts( - supabase: any, + supabase: SupabaseClient, products: ProductPayload[], ): Promise<{ created: number; updated: number; failed: number; errors: string[] }> { let created = 0; diff --git a/supabase/migrations/20260518121325_backfill_user_organizations_orphans.sql b/supabase/migrations/20260518121325_backfill_user_organizations_orphans.sql index 39255cb45..f09e65ea2 100644 --- a/supabase/migrations/20260518121325_backfill_user_organizations_orphans.sql +++ b/supabase/migrations/20260518121325_backfill_user_organizations_orphans.sql @@ -4,7 +4,9 @@ INSERT INTO public.user_organizations (user_id, organization_id, role) SELECT u.id, - '5db5aee1-064b-4ef4-9193-345dcd8274ea'::uuid, + o.id, (CASE WHEN u.email = 'cad01@promobrindes.com.br' THEN 'admin' ELSE 'member' END)::org_role FROM auth.users u +JOIN public.organizations o + ON o.id = '5db5aee1-064b-4ef4-9193-345dcd8274ea'::uuid WHERE u.id NOT IN (SELECT user_id FROM public.user_organizations); diff --git a/supabase/migrations/20260518122107_reset_comercial01_password_test.sql b/supabase/migrations/20260518122107_reset_comercial01_password_test.sql index 191d0d9fc..ed89e24bc 100644 --- a/supabase/migrations/20260518122107_reset_comercial01_password_test.sql +++ b/supabase/migrations/20260518122107_reset_comercial01_password_test.sql @@ -1,4 +1,2 @@ --- Reset cirurgico: comercial01 para teste -UPDATE auth.users -SET encrypted_password = crypt('@Promobrindes2021', gen_salt('bf')), updated_at = now() -WHERE email = 'comercial01@promobrindes.com.br'; +-- Segurança: migrations não devem conter reset de senha hardcoded. +-- Mantido intencionalmente sem operações para preservar a ordem de versões. diff --git a/supabase/migrations/20260522012527_harden_frontend_telemetry_rls_sec_002.sql b/supabase/migrations/20260522012527_harden_frontend_telemetry_rls_sec_002.sql index 52fff4dea..f3c97b88c 100644 --- a/supabase/migrations/20260522012527_harden_frontend_telemetry_rls_sec_002.sql +++ b/supabase/migrations/20260522012527_harden_frontend_telemetry_rls_sec_002.sql @@ -1,4 +1,5 @@ BEGIN; +DROP POLICY IF EXISTS "Anyone can insert telemetry" ON public.frontend_telemetry; DROP POLICY IF EXISTS frontend_telemetry_insert_anon ON public.frontend_telemetry; DROP POLICY IF EXISTS frontend_telemetry_insert_authenticated ON public.frontend_telemetry; CREATE POLICY frontend_telemetry_insert_anon ON public.frontend_telemetry FOR INSERT TO anon diff --git a/supabase/migrations/20260522113220_lote_a_01_public_token_failures.sql b/supabase/migrations/20260522113220_lote_a_01_public_token_failures.sql index 8c4f4e264..e23f2d77e 100644 --- a/supabase/migrations/20260522113220_lote_a_01_public_token_failures.sql +++ b/supabase/migrations/20260522113220_lote_a_01_public_token_failures.sql @@ -1,5 +1,5 @@ -- LOTE A 1/6 - public_token_failures (origem: Lovable Cloud) -CREATE TABLE public.public_token_failures ( +CREATE TABLE IF NOT EXISTS public.public_token_failures ( id uuid NOT NULL DEFAULT gen_random_uuid(), resource_type text NOT NULL, resource_id text NULL, @@ -11,8 +11,33 @@ CREATE TABLE public.public_token_failures ( CONSTRAINT public_token_failures_pkey PRIMARY KEY (id), CONSTRAINT public_token_failures_resource_type_check CHECK (resource_type = ANY (ARRAY['quote'::text, 'kit'::text])) ); -CREATE INDEX idx_public_token_failures_ip ON public.public_token_failures USING btree (ip_address, created_at DESC); -CREATE INDEX idx_public_token_failures_resource ON public.public_token_failures USING btree (resource_type, resource_id, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_public_token_failures_ip ON public.public_token_failures USING btree (ip_address, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_public_token_failures_resource ON public.public_token_failures USING btree (resource_type, resource_id, created_at DESC); ALTER TABLE public.public_token_failures ENABLE ROW LEVEL SECURITY; -CREATE POLICY "Service role inserts token failures" ON public.public_token_failures FOR INSERT TO service_role WITH CHECK (true); -CREATE POLICY "Admins read token failures" ON public.public_token_failures FOR SELECT TO authenticated USING (is_admin(auth.uid())); +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_policies + WHERE schemaname = 'public' + AND tablename = 'public_token_failures' + AND policyname = 'Service role inserts token failures' + ) THEN + CREATE POLICY "Service role inserts token failures" ON public.public_token_failures FOR INSERT TO service_role WITH CHECK (true); + END IF; +END +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_policies + WHERE schemaname = 'public' + AND tablename = 'public_token_failures' + AND policyname = 'Admins read token failures' + ) THEN + CREATE POLICY "Admins read token failures" ON public.public_token_failures FOR SELECT TO authenticated USING (is_admin(auth.uid())); + END IF; +END +$$; diff --git a/supabase/migrations/20260522113233_lote_a_02_user_known_devices.sql b/supabase/migrations/20260522113233_lote_a_02_user_known_devices.sql index 518123498..344545013 100644 --- a/supabase/migrations/20260522113233_lote_a_02_user_known_devices.sql +++ b/supabase/migrations/20260522113233_lote_a_02_user_known_devices.sql @@ -1,5 +1,5 @@ -- LOTE A 2/6 - user_known_devices -CREATE TABLE public.user_known_devices ( +CREATE TABLE IF NOT EXISTS public.user_known_devices ( id uuid NOT NULL DEFAULT gen_random_uuid(), user_id uuid NOT NULL, fingerprint text NOT NULL, @@ -9,8 +9,10 @@ CREATE TABLE public.user_known_devices ( CONSTRAINT user_known_devices_pkey PRIMARY KEY (id), CONSTRAINT user_known_devices_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE ); -CREATE INDEX idx_user_known_devices_fingerprint ON public.user_known_devices USING btree (fingerprint); -CREATE INDEX idx_user_known_devices_user_id ON public.user_known_devices USING btree (user_id); +CREATE INDEX IF NOT EXISTS idx_user_known_devices_fingerprint ON public.user_known_devices USING btree (fingerprint); +CREATE INDEX IF NOT EXISTS idx_user_known_devices_user_id ON public.user_known_devices USING btree (user_id); ALTER TABLE public.user_known_devices ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Users can manage their own devices" ON public.user_known_devices; CREATE POLICY "Users can manage their own devices" ON public.user_known_devices FOR ALL TO public USING (auth.uid() = user_id); +DROP POLICY IF EXISTS "Users can view their own devices" ON public.user_known_devices; CREATE POLICY "Users can view their own devices" ON public.user_known_devices FOR SELECT TO public USING (auth.uid() = user_id); diff --git a/supabase/migrations/20260522113250_lote_a_03_password_reset_requests.sql b/supabase/migrations/20260522113250_lote_a_03_password_reset_requests.sql index fb789c344..bba905050 100644 --- a/supabase/migrations/20260522113250_lote_a_03_password_reset_requests.sql +++ b/supabase/migrations/20260522113250_lote_a_03_password_reset_requests.sql @@ -1,5 +1,5 @@ -- LOTE A 3/6 - password_reset_requests -CREATE TABLE public.password_reset_requests ( +CREATE TABLE IF NOT EXISTS public.password_reset_requests ( id uuid NOT NULL DEFAULT gen_random_uuid(), email text NOT NULL, status text NOT NULL DEFAULT 'pending', @@ -13,11 +13,14 @@ CREATE TABLE public.password_reset_requests ( CONSTRAINT password_reset_requests_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id), CONSTRAINT password_reset_requests_status_check CHECK (status = ANY (ARRAY['pending','approved','rejected'])) ); -CREATE INDEX idx_password_reset_requests_email ON public.password_reset_requests USING btree (email); -CREATE INDEX idx_password_reset_requests_status ON public.password_reset_requests USING btree (status); +CREATE INDEX IF NOT EXISTS idx_password_reset_requests_email ON public.password_reset_requests USING btree (email); +CREATE INDEX IF NOT EXISTS idx_password_reset_requests_status ON public.password_reset_requests USING btree (status); ALTER TABLE public.password_reset_requests ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Anyone can request a password reset" ON public.password_reset_requests; CREATE POLICY "Anyone can request a password reset" ON public.password_reset_requests FOR INSERT TO public WITH CHECK (true); +DROP POLICY IF EXISTS "Admins can view password reset requests" ON public.password_reset_requests; CREATE POLICY "Admins can view password reset requests" ON public.password_reset_requests FOR SELECT TO public USING (EXISTS (SELECT 1 FROM public.user_roles WHERE user_roles.user_id = auth.uid() AND user_roles.role = ANY (ARRAY['dev'::app_role,'supervisor'::app_role,'admin'::app_role]))); +DROP POLICY IF EXISTS "Admins can update password reset requests" ON public.password_reset_requests; CREATE POLICY "Admins can update password reset requests" ON public.password_reset_requests FOR UPDATE TO public USING (EXISTS (SELECT 1 FROM public.user_roles WHERE user_roles.user_id = auth.uid() AND user_roles.role = ANY (ARRAY['dev'::app_role,'supervisor'::app_role,'admin'::app_role]))); diff --git a/supabase/migrations/20260522113314_lote_a_04_quote_approval_tokens.sql b/supabase/migrations/20260522113314_lote_a_04_quote_approval_tokens.sql index 28311f5b7..9e7a010e3 100644 --- a/supabase/migrations/20260522113314_lote_a_04_quote_approval_tokens.sql +++ b/supabase/migrations/20260522113314_lote_a_04_quote_approval_tokens.sql @@ -1,5 +1,5 @@ -- LOTE A 4/6 - quote_approval_tokens -CREATE TABLE public.quote_approval_tokens ( +CREATE TABLE IF NOT EXISTS public.quote_approval_tokens ( id uuid NOT NULL DEFAULT gen_random_uuid(), quote_id text NOT NULL, token text NOT NULL DEFAULT encode(gen_random_bytes(32),'hex'), @@ -16,16 +16,34 @@ CREATE TABLE public.quote_approval_tokens ( CONSTRAINT quote_approval_tokens_token_key UNIQUE (token), CONSTRAINT quote_approval_tokens_seller_id_fkey FOREIGN KEY (seller_id) REFERENCES auth.users(id) ON DELETE CASCADE ); -CREATE INDEX idx_approval_tokens_quote ON public.quote_approval_tokens(quote_id); -CREATE INDEX idx_approval_tokens_token_status ON public.quote_approval_tokens(token,status) WHERE status='active'; -CREATE INDEX idx_quote_approval_tokens_seller_id ON public.quote_approval_tokens(seller_id); -CREATE TRIGGER trg_generate_secure_approval_token BEFORE INSERT ON public.quote_approval_tokens FOR EACH ROW EXECUTE FUNCTION public.generate_secure_token(); -CREATE TRIGGER trg_invalidate_used_approval_token BEFORE UPDATE ON public.quote_approval_tokens FOR EACH ROW EXECUTE FUNCTION public.invalidate_used_approval_token(); -CREATE TRIGGER trg_notify_quote_client_response AFTER UPDATE ON public.quote_approval_tokens FOR EACH ROW EXECUTE FUNCTION public.notify_quote_client_response(); -CREATE TRIGGER trg_owner__quote_approval_tokens__seller_id BEFORE INSERT ON public.quote_approval_tokens FOR EACH ROW EXECUTE FUNCTION public.enforce_seller_id_owner(); -CREATE TRIGGER trg_validate_approval_token_status BEFORE INSERT OR UPDATE ON public.quote_approval_tokens FOR EACH ROW EXECUTE FUNCTION public.validate_status_fields(); +CREATE INDEX IF NOT EXISTS idx_approval_tokens_quote ON public.quote_approval_tokens(quote_id); +CREATE INDEX IF NOT EXISTS idx_approval_tokens_token_status ON public.quote_approval_tokens(token,status) WHERE status='active'; +CREATE INDEX IF NOT EXISTS idx_quote_approval_tokens_seller_id ON public.quote_approval_tokens(seller_id); +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname='trg_generate_secure_approval_token' AND tgrelid='public.quote_approval_tokens'::regclass) THEN + CREATE TRIGGER trg_generate_secure_approval_token BEFORE INSERT ON public.quote_approval_tokens FOR EACH ROW EXECUTE FUNCTION public.generate_secure_token(); +END IF; END $$; +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname='trg_invalidate_used_approval_token' AND tgrelid='public.quote_approval_tokens'::regclass) THEN + CREATE TRIGGER trg_invalidate_used_approval_token BEFORE UPDATE ON public.quote_approval_tokens FOR EACH ROW EXECUTE FUNCTION public.invalidate_used_approval_token(); +END IF; END $$; +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname='trg_notify_quote_client_response' AND tgrelid='public.quote_approval_tokens'::regclass) THEN + CREATE TRIGGER trg_notify_quote_client_response AFTER UPDATE ON public.quote_approval_tokens FOR EACH ROW EXECUTE FUNCTION public.notify_quote_client_response(); +END IF; END $$; +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname='trg_owner__quote_approval_tokens__seller_id' AND tgrelid='public.quote_approval_tokens'::regclass) THEN + CREATE TRIGGER trg_owner__quote_approval_tokens__seller_id BEFORE INSERT ON public.quote_approval_tokens FOR EACH ROW EXECUTE FUNCTION public.enforce_seller_id_owner(); +END IF; END $$; +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname='trg_validate_approval_token_status' AND tgrelid='public.quote_approval_tokens'::regclass) THEN + CREATE TRIGGER trg_validate_approval_token_status BEFORE INSERT OR UPDATE ON public.quote_approval_tokens FOR EACH ROW EXECUTE FUNCTION public.validate_status_fields(); +END IF; END $$; ALTER TABLE public.quote_approval_tokens ENABLE ROW LEVEL SECURITY; -CREATE POLICY qatokens_select_scope ON public.quote_approval_tokens FOR SELECT TO authenticated USING (can_view_all_sales() OR seller_id = auth.uid()); -CREATE POLICY qatokens_insert_scope ON public.quote_approval_tokens FOR INSERT TO authenticated WITH CHECK (can_view_all_sales() OR seller_id = auth.uid()); -CREATE POLICY qatokens_update_scope ON public.quote_approval_tokens FOR UPDATE TO authenticated USING (can_view_all_sales() OR seller_id = auth.uid()) WITH CHECK (can_view_all_sales() OR seller_id = auth.uid()); -CREATE POLICY qatokens_delete_scope ON public.quote_approval_tokens FOR DELETE TO authenticated USING (can_view_all_sales() OR seller_id = auth.uid()); +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='quote_approval_tokens' AND policyname='qatokens_select_scope') THEN + CREATE POLICY qatokens_select_scope ON public.quote_approval_tokens FOR SELECT TO authenticated USING (can_view_all_sales() OR seller_id = auth.uid()); +END IF; END $$; +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='quote_approval_tokens' AND policyname='qatokens_insert_scope') THEN + CREATE POLICY qatokens_insert_scope ON public.quote_approval_tokens FOR INSERT TO authenticated WITH CHECK (can_view_all_sales() OR seller_id = auth.uid()); +END IF; END $$; +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='quote_approval_tokens' AND policyname='qatokens_update_scope') THEN + CREATE POLICY qatokens_update_scope ON public.quote_approval_tokens FOR UPDATE TO authenticated USING (can_view_all_sales() OR seller_id = auth.uid()) WITH CHECK (can_view_all_sales() OR seller_id = auth.uid()); +END IF; END $$; +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='quote_approval_tokens' AND policyname='qatokens_delete_scope') THEN + CREATE POLICY qatokens_delete_scope ON public.quote_approval_tokens FOR DELETE TO authenticated USING (can_view_all_sales() OR seller_id = auth.uid()); +END IF; END $$; diff --git a/supabase/migrations/20260522113351_lote_a_06_funcoes_step_up.sql b/supabase/migrations/20260522113351_lote_a_06_funcoes_step_up.sql index 6f8006652..35dd4493d 100644 --- a/supabase/migrations/20260522113351_lote_a_06_funcoes_step_up.sql +++ b/supabase/migrations/20260522113351_lote_a_06_funcoes_step_up.sql @@ -1,10 +1,18 @@ -- LOTE A 6/6 - step-up auth functions CREATE OR REPLACE FUNCTION public.start_step_up_challenge(_action text, _target_ref text DEFAULT NULL) RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ -DECLARE v_id uuid; +DECLARE + v_id uuid; + v_uid uuid := auth.uid(); + v_otp_hash text; BEGIN - IF auth.uid() IS NULL THEN RAISE EXCEPTION 'Nao autenticado'; END IF; - INSERT INTO public.step_up_challenges (user_id,action,target_ref) VALUES (auth.uid(),_action,_target_ref) RETURNING id INTO v_id; + IF v_uid IS NULL THEN RAISE EXCEPTION 'Nao autenticado'; END IF; + + v_otp_hash := encode(digest(gen_random_uuid()::text || clock_timestamp()::text || v_uid::text, 'sha256'), 'hex'); + + INSERT INTO public.step_up_challenges (user_id, action, target_ref, otp_hash) + VALUES (v_uid, _action, _target_ref, v_otp_hash) + RETURNING id INTO v_id; RETURN v_id; END; $$; REVOKE EXECUTE ON FUNCTION public.start_step_up_challenge(text,text) FROM PUBLIC, anon; @@ -12,9 +20,27 @@ GRANT EXECUTE ON FUNCTION public.start_step_up_challenge(text,text) TO authentic CREATE OR REPLACE FUNCTION public.verify_step_up_password(_challenge_id uuid, _password_attempt text) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ +DECLARE + v_uid uuid := auth.uid(); + v_password_ok boolean; BEGIN + IF v_uid IS NULL THEN + RAISE EXCEPTION 'Nao autenticado'; + END IF; + + SELECT EXISTS ( + SELECT 1 + FROM auth.users + WHERE id = v_uid + AND encrypted_password = crypt(_password_attempt, encrypted_password) + ) INTO v_password_ok; + + IF NOT v_password_ok THEN + RETURN false; + END IF; + UPDATE public.step_up_challenges SET password_verified=true - WHERE id=_challenge_id AND user_id=auth.uid() AND consumed=false AND expires_at > now(); + WHERE id=_challenge_id AND user_id=v_uid AND consumed=false AND expires_at > now(); RETURN FOUND; END; $$; REVOKE EXECUTE ON FUNCTION public.verify_step_up_password(uuid,text) FROM PUBLIC, anon; diff --git a/supabase/migrations/20260522113832_lote_b_01_edge_rate_limits.sql b/supabase/migrations/20260522113832_lote_b_01_edge_rate_limits.sql index 4b8e87727..5eee10a9f 100644 --- a/supabase/migrations/20260522113832_lote_b_01_edge_rate_limits.sql +++ b/supabase/migrations/20260522113832_lote_b_01_edge_rate_limits.sql @@ -1,5 +1,5 @@ -- LOTE B 1/2 - edge_rate_limits -CREATE TABLE public.edge_rate_limits ( +CREATE TABLE IF NOT EXISTS public.edge_rate_limits ( key text NOT NULL, count integer NOT NULL DEFAULT 0, reset_at timestamptz NOT NULL, @@ -7,7 +7,8 @@ CREATE TABLE public.edge_rate_limits ( updated_at timestamptz NULL DEFAULT now(), CONSTRAINT edge_rate_limits_pkey PRIMARY KEY (key) ); -CREATE INDEX idx_edge_rate_limits_reset_at ON public.edge_rate_limits(reset_at); +CREATE INDEX IF NOT EXISTS idx_edge_rate_limits_reset_at ON public.edge_rate_limits(reset_at); ALTER TABLE public.edge_rate_limits ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS "Service role can do everything on edge_rate_limits" ON public.edge_rate_limits; CREATE POLICY "Service role can do everything on edge_rate_limits" ON public.edge_rate_limits FOR ALL TO public USING (auth.role()='service_role') WITH CHECK (auth.role()='service_role'); diff --git a/supabase/migrations/20260522114122_lote_c_02_kit_share_tokens.sql b/supabase/migrations/20260522114122_lote_c_02_kit_share_tokens.sql index 4dfe07a6b..4ac3914af 100644 --- a/supabase/migrations/20260522114122_lote_c_02_kit_share_tokens.sql +++ b/supabase/migrations/20260522114122_lote_c_02_kit_share_tokens.sql @@ -1,5 +1,5 @@ -- LOTE C 2/2 - kit_share_tokens -CREATE TABLE public.kit_share_tokens ( +CREATE TABLE IF NOT EXISTS public.kit_share_tokens ( id uuid NOT NULL DEFAULT gen_random_uuid(), kit_id uuid NOT NULL, seller_id uuid NOT NULL, token text NOT NULL DEFAULT encode(gen_random_bytes(32),'hex'), @@ -12,10 +12,25 @@ CREATE TABLE public.kit_share_tokens ( CONSTRAINT kit_share_tokens_token_key UNIQUE (token), CONSTRAINT kit_share_tokens_kit_id_fkey FOREIGN KEY (kit_id) REFERENCES public.custom_kits(id) ON DELETE CASCADE ); -CREATE INDEX idx_kit_share_tokens_kit_id ON public.kit_share_tokens(kit_id); -CREATE TRIGGER trg_owner__kit_share_tokens__seller_id BEFORE INSERT ON public.kit_share_tokens FOR EACH ROW EXECUTE FUNCTION public.enforce_seller_id_owner(); -CREATE TRIGGER trg_validate_kit_share_token_status BEFORE INSERT OR UPDATE ON public.kit_share_tokens FOR EACH ROW EXECUTE FUNCTION public.validate_status_fields(); -CREATE TRIGGER trg_dispatch_webhook_kit_share AFTER INSERT ON public.kit_share_tokens FOR EACH ROW EXECUTE FUNCTION public.dispatch_quote_webhook_event(); +CREATE INDEX IF NOT EXISTS idx_kit_share_tokens_kit_id ON public.kit_share_tokens(kit_id); +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname='trg_owner__kit_share_tokens__seller_id' AND tgrelid='public.kit_share_tokens'::regclass) THEN + CREATE TRIGGER trg_owner__kit_share_tokens__seller_id BEFORE INSERT ON public.kit_share_tokens FOR EACH ROW EXECUTE FUNCTION public.enforce_seller_id_owner(); +END IF; END $$; +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname='trg_validate_kit_share_token_status' AND tgrelid='public.kit_share_tokens'::regclass) THEN + CREATE TRIGGER trg_validate_kit_share_token_status BEFORE INSERT OR UPDATE ON public.kit_share_tokens FOR EACH ROW EXECUTE FUNCTION public.validate_status_fields(); +END IF; END $$; +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname='trg_dispatch_webhook_kit_share' AND tgrelid='public.kit_share_tokens'::regclass) THEN + CREATE TRIGGER trg_dispatch_webhook_kit_share AFTER INSERT ON public.kit_share_tokens FOR EACH ROW EXECUTE FUNCTION public.dispatch_quote_webhook_event(); +END IF; END $$; ALTER TABLE public.kit_share_tokens ENABLE ROW LEVEL SECURITY; -CREATE POLICY "Sellers can manage own kit share tokens" ON public.kit_share_tokens FOR ALL TO authenticated - USING (seller_id=auth.uid()) WITH CHECK (seller_id=auth.uid()); +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_policies + WHERE schemaname='public' AND tablename='kit_share_tokens' AND policyname='Sellers can manage own kit share tokens' + ) THEN + CREATE POLICY "Sellers can manage own kit share tokens" ON public.kit_share_tokens FOR ALL TO authenticated + USING (seller_id=auth.uid()) WITH CHECK (seller_id=auth.uid()); + END IF; +END +$$; diff --git a/supabase/migrations/20260522143654_wave_3_2_a_orders_oficial_align_client_v2.sql b/supabase/migrations/20260522143654_wave_3_2_a_orders_oficial_align_client_v2.sql index aca51efa5..4a37cd4e8 100644 --- a/supabase/migrations/20260522143654_wave_3_2_a_orders_oficial_align_client_v2.sql +++ b/supabase/migrations/20260522143654_wave_3_2_a_orders_oficial_align_client_v2.sql @@ -12,7 +12,48 @@ BEGIN RETURN COALESCE(NEW,OLD); END; $$; -ALTER TABLE public.orders DROP COLUMN total; -ALTER TABLE public.orders RENAME COLUMN total_amount TO total; -ALTER TABLE public.orders DROP COLUMN IF EXISTS customer_name, DROP COLUMN IF EXISTS customer_email, DROP COLUMN IF EXISTS customer_phone, DROP COLUMN IF EXISTS tracking_code; +ALTER TABLE public.orders DROP COLUMN IF EXISTS total; +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema='public' AND table_name='orders' AND column_name='total_amount' + ) AND NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema='public' AND table_name='orders' AND column_name='total' + ) THEN + ALTER TABLE public.orders RENAME COLUMN total_amount TO total; + END IF; +END +$$; +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema='public' AND table_name='orders' AND column_name='tracking_code' + ) AND EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema='public' AND table_name='orders' AND column_name='tracking_number' + ) THEN + UPDATE public.orders + SET tracking_number = COALESCE(NULLIF(tracking_number, ''), tracking_code) + WHERE tracking_code IS NOT NULL; + END IF; +END +$$; +ALTER TABLE public.orders DROP COLUMN IF EXISTS customer_name, DROP COLUMN IF EXISTS customer_email, DROP COLUMN IF EXISTS customer_phone; ALTER TABLE public.orders ADD COLUMN IF NOT EXISTS client_email text, ADD COLUMN IF NOT EXISTS client_phone text, ADD COLUMN IF NOT EXISTS tracking_number text, ADD COLUMN IF NOT EXISTS shipping_type text, ADD COLUMN IF NOT EXISTS delivery_time text; +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema='public' AND table_name='orders' AND column_name='tracking_code' + ) THEN + UPDATE public.orders + SET tracking_number = COALESCE(NULLIF(tracking_number, ''), tracking_code) + WHERE tracking_code IS NOT NULL; + + ALTER TABLE public.orders DROP COLUMN tracking_code; + END IF; +END +$$; diff --git a/supabase/migrations/20260522145140_wave_3_3_c_1_oficial_quote_templates_unificado.sql b/supabase/migrations/20260522145140_wave_3_3_c_1_oficial_quote_templates_unificado.sql index 05b09f8a4..dc2eb1dd2 100644 --- a/supabase/migrations/20260522145140_wave_3_3_c_1_oficial_quote_templates_unificado.sql +++ b/supabase/migrations/20260522145140_wave_3_3_c_1_oficial_quote_templates_unificado.sql @@ -1,7 +1,28 @@ -- Wave 3.3.C.1 - quote_templates unificado -ALTER TABLE public.quote_templates RENAME COLUMN default_payment_terms TO payment_terms; -ALTER TABLE public.quote_templates RENAME COLUMN default_delivery_terms TO delivery_time; -ALTER TABLE public.quote_templates RENAME COLUMN default_notes TO notes; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='quote_templates' AND column_name='default_payment_terms') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='quote_templates' AND column_name='payment_terms') THEN + ALTER TABLE public.quote_templates RENAME COLUMN default_payment_terms TO payment_terms; + END IF; +END +$$; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='quote_templates' AND column_name='default_delivery_terms') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='quote_templates' AND column_name='delivery_time') THEN + ALTER TABLE public.quote_templates RENAME COLUMN default_delivery_terms TO delivery_time; + END IF; +END +$$; +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='quote_templates' AND column_name='default_notes') + AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='quote_templates' AND column_name='notes') THEN + ALTER TABLE public.quote_templates RENAME COLUMN default_notes TO notes; + END IF; +END +$$; ALTER TABLE public.quote_templates ADD COLUMN IF NOT EXISTS seller_id uuid, ADD COLUMN IF NOT EXISTS template_data jsonb DEFAULT '{}'::jsonb, diff --git a/tests/integrations/supabase/client-import-safe.test.ts b/tests/integrations/supabase/client-import-safe.test.ts new file mode 100644 index 000000000..d96aa3c29 --- /dev/null +++ b/tests/integrations/supabase/client-import-safe.test.ts @@ -0,0 +1,14 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +describe('supabase client import safety', () => { + afterEach(() => { + vi.unstubAllGlobals(); + vi.resetModules(); + }); + + it('imports safely when window is undefined', async () => { + vi.stubGlobal('window', undefined); + + await expect(import('@/integrations/supabase/client')).resolves.toHaveProperty('supabase'); + }); +}); diff --git a/tests/lib/cloud-status.test.ts b/tests/lib/cloud-status.test.ts index bf16e0230..081901c01 100644 --- a/tests/lib/cloud-status.test.ts +++ b/tests/lib/cloud-status.test.ts @@ -1,5 +1,10 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; +const loggerWarnMock = vi.fn(); +vi.mock('@/lib/logger', () => ({ + logger: { warn: (...args: unknown[]) => loggerWarnMock(...args) }, +})); + // Mock supabase client + pingHealth ANTES de importar o módulo testado. const getSessionMock = vi.fn(); vi.mock('@/integrations/supabase/client', () => ({ @@ -27,6 +32,8 @@ beforeEach(() => { getSessionMock.mockReset(); pingHealthMock.mockReset(); fetchMock.mockReset(); + loggerWarnMock.mockReset(); + localStorage.clear(); }); describe('cloud-status', () => { @@ -84,6 +91,33 @@ describe('cloud-status', () => { expect(fetchMock).toHaveBeenCalledTimes(1); }); + + it('does not fail probe when history persistence throws', async () => { + getSessionMock.mockResolvedValue({ error: null }); + pingHealthMock.mockResolvedValue({ ok: true, ms: 100 }); + fetchMock.mockResolvedValue({ ok: true, status: 200 } as Response); + + const setItemSpy = vi + .spyOn(Storage.prototype, 'setItem') + .mockImplementation(() => { + throw new Error('quota exceeded'); + }); + + const snap = await probeCloudStatus(true); + + expect(snap.status).toBe('healthy'); + expect(snap.signals.auth.ok).toBe(true); + expect(loggerWarnMock).toHaveBeenCalledWith( + '[CloudStatus] failed to persist status history', + expect.objectContaining({ + HISTORY_KEY: 'supabase_health_history', + error: 'quota exceeded', + }), + ); + + setItemSpy.mockRestore(); + }); + it('ensureCloudReady throws CloudNotReadyError when persistently degraded', async () => { getSessionMock.mockResolvedValue({ error: new Error('x') }); pingHealthMock.mockResolvedValue({ ok: false, ms: 0, error: 'x' });