diff --git a/src/hooks/products/useCatalogPrefetch.ts b/src/hooks/products/useCatalogPrefetch.ts index 9f0da8f44..aebbadc58 100644 --- a/src/hooks/products/useCatalogPrefetch.ts +++ b/src/hooks/products/useCatalogPrefetch.ts @@ -1,8 +1,13 @@ import { useEffect, useRef } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { useAuth } from '@/contexts/AuthContext'; -import { invokeBatchBridge } from '@/lib/external-db'; -import { mapLightweightToProduct, PRODUCT_SELECT_LIGHTWEIGHT, CATALOG_PAGE_SIZE, CATALOG_BATCH_PAGES } from '@/hooks/products'; +import { invokeBatchBridge, fetchPromobrindCategories } from '@/lib/external-db'; +import { + mapLightweightToProduct, + PRODUCT_SELECT_LIGHTWEIGHT, + CATALOG_PAGE_SIZE, + CATALOG_BATCH_PAGES, +} from '@/hooks/products'; /** * Prefetch do catálogo SOMENTE após autenticação (#6). @@ -15,7 +20,7 @@ export function useCatalogPrefetch() { useEffect(() => { if (isLoading || !isAuthenticated || prefetchedRef.current) return; - + // Otimização: Delay de 400ms para prefetch não competir com o render inicial crítico (LCP), // mas rápido o suficiente para estar pronto antes que o usuário interaja. const timer = setTimeout(() => { @@ -33,13 +38,24 @@ export function useCatalogPrefetch() { offset: i * CATALOG_PAGE_SIZE, ...(i === 0 ? { countMode: 'exact' } : {}), })); - const batchResults = await invokeBatchBridge(batchQueries); + const [batchResults, categoriesRaw] = await Promise.all([ + invokeBatchBridge(batchQueries), + fetchPromobrindCategories().catch(() => [] as { id: string; name: string }[]), + ]); + const categoriesById = new Map(categoriesRaw.map((c) => [String(c.id), c.name])); const products: ReturnType[] = []; let totalEstimate: number | null = null; let lastPageSize = 0; for (const result of batchResults) { if (result.success && result.data?.records) { - products.push(...(result.data.records as unknown[]).map(mapLightweightToProduct)); + products.push( + ...(result.data.records as unknown[]).map((p) => + mapLightweightToProduct( + p as Parameters[0], + categoriesById, + ), + ), + ); lastPageSize = result.data.records.length; if (result.data.count !== null && totalEstimate === null) { totalEstimate = result.data.count as number; @@ -48,7 +64,8 @@ export function useCatalogPrefetch() { } return { products, - nextOffset: lastPageSize === CATALOG_PAGE_SIZE ? CATALOG_BATCH_PAGES * CATALOG_PAGE_SIZE : null, + nextOffset: + lastPageSize === CATALOG_PAGE_SIZE ? CATALOG_BATCH_PAGES * CATALOG_PAGE_SIZE : null, totalEstimate, }; }, @@ -59,4 +76,4 @@ export function useCatalogPrefetch() { return () => clearTimeout(timer); }, [isAuthenticated, isLoading, queryClient]); -} \ No newline at end of file +} diff --git a/src/hooks/products/useProductsLightweight.ts b/src/hooks/products/useProductsLightweight.ts index ce1eafa08..8a90749e0 100644 --- a/src/hooks/products/useProductsLightweight.ts +++ b/src/hooks/products/useProductsLightweight.ts @@ -7,6 +7,7 @@ import { useQuery, useInfiniteQuery } from '@tanstack/react-query'; import { fetchPromobrindProductsLightweight, invokeBatchBridge, + fetchPromobrindCategories, type LightweightProduct, } from '@/lib/external-db'; @@ -38,17 +39,24 @@ function getStockStatus(stock: number): 'in-stock' | 'low-stock' | 'out-of-stock return 'in-stock'; } -export function mapLightweightToProduct(p: LightweightProduct): Product { +export function mapLightweightToProduct( + p: LightweightProduct, + categoriesById?: ReadonlyMap, +): Product { const imageUrl = p.primary_image_url || p.image_url || '/placeholder.svg'; const price = p.sale_price ?? p.cost_price ?? 0; const stock = p.stock_quantity || 0; + const resolvedCategoryId = p.category_id || p.main_category_id; + const resolvedCategoryName = resolvedCategoryId + ? (categoriesById?.get(resolvedCategoryId) ?? null) + : null; return { id: p.id, name: p.name, description: '', - category_id: p.category_id || p.main_category_id, - category_name: null, + category_id: resolvedCategoryId, + category_name: resolvedCategoryName, price: typeof price === 'number' ? price : 0, image_url: imageUrl, images: [imageUrl], @@ -67,8 +75,8 @@ export function mapLightweightToProduct(p: LightweightProduct): Product { isKit: p.is_kit ?? false, gender: p.gender || null, category: { - id: p.category_id || p.main_category_id || '0', - name: 'Sem categoria', + id: resolvedCategoryId || '0', + name: resolvedCategoryName ?? 'Sem categoria', }, supplier: { id: p.supplier_id || p.brand || 'unknown', @@ -113,6 +121,15 @@ interface CatalogPage { * First call fetches 4 pages (2000 products) via batch bridge. * Subsequent calls fetch 1 page (500 products) each. */ +async function loadCategoriesMap(): Promise> { + try { + const categories = await fetchPromobrindCategories(); + return new Map(categories.map((c) => [String(c.id), c.name])); + } catch { + return new Map(); + } +} + async function fetchCatalogPage(offset: number, search?: string): Promise { const filters: Record = { active: true }; if (search) filters._search = search; @@ -132,32 +149,40 @@ async function fetchCatalogPage(offset: number, search?: string): Promise mapLightweightToProduct(p, categoriesById)), nextOffset: fallbackProducts.length === CATALOG_PAGE_SIZE ? offset + CATALOG_PAGE_SIZE : null, totalEstimate: null, }; } + const categoriesById = await categoriesPromise; const products: Product[] = []; let totalEstimate: number | null = null; let lastPageSize = 0; for (const result of batchResults) { if (result.success && result.data?.records) { - const mapped = (result.data.records as LightweightProduct[]).map(mapLightweightToProduct); + const mapped = (result.data.records as LightweightProduct[]).map((p) => + mapLightweightToProduct(p, categoriesById), + ); products.push(...mapped); lastPageSize = result.data.records.length; if (result.data.count !== null && totalEstimate === null) {