"use client"; import { useState, useEffect, useCallback, useMemo, Suspense } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { Search, ArrowLeft, Loader2, Database, Leaf, SlidersHorizontal, LayoutGrid, List, } from "lucide-react"; import { ThemeToggle } from "@/components/theme-toggle"; import { Button } from "@/components/ui/button"; import { Sheet, SheetContent, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; import { FilterSidebar } from "@/app/components/FilterSidebar"; import { ProductCard } from "@/app/components/ProductCard"; import { ProductCardSkeleton } from "@/app/components/ProductCardSkeleton"; import { DonateCard } from "@/app/components/DonateCard"; import { SearchBar } from "@/app/components/SearchBar"; import { Pagination } from "@/app/components/Pagination"; import { useProductFilters } from "@/hooks/useProductFilters"; import { useSearchCache } from "@/hooks/useSearchCache"; import { useStorageMonitor } from "@/hooks/useStorageMonitor"; import { useCacheOnlyMode } from "@/hooks/useCacheOnlyMode"; import { CacheOnlySwitch } from "@/app/components/CacheOnlySwitch"; import { detectUserCountry } from "@/lib/geolocation"; import { FilterRecord } from "@/lib/cacheManager"; import { ProductFilters } from "@/lib/types"; import Link from "next/link"; import { Product, sortProducts } from "@/lib/openFoodFactsApi"; function convertFiltersToRecord(filters: ProductFilters): FilterRecord { const record: FilterRecord = {}; Object.entries(filters).forEach(([key, value]) => { if (value !== undefined && value !== null) { record[key] = value as FilterRecord[string]; } }); return record; } /* ── Grade filter badge ──────────────────────────────────────────────────── */ const GRADE_COLORS: Record = { a: { bg: "#038141", ink: "#fff" }, b: { bg: "#85bb2f", ink: "#fff" }, c: { bg: "#fecb02", ink: "#3a2a00" }, d: { bg: "#ee8100", ink: "#fff" }, e: { bg: "#e63e11", ink: "#fff" }, }; function ResultadosContent() { const searchParams = useSearchParams(); const router = useRouter(); const query = searchParams.get("q") || ""; const countryParam = searchParams.get("country") || ""; const [allProducts, setAllProducts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [detectedCountry, setDetectedCountry] = useState(""); const [forceRefresh, setForceRefresh] = useState(false); const [gradeFilter, setGradeFilter] = useState(null); const [allergenFree, setAllergenFree] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(20); const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); const [showSort, setShowSort] = useState(false); const [showCacheInfo, setShowCacheInfo] = useState(false); const [showFilterSheet, setShowFilterSheet] = useState(false); const { state: cacheState, getCachedSearch, saveSearch, clearCache, setError: setCacheError, setLoading: setCacheLoading, } = useSearchCache(); const { filters, updateFilters, clearFilters, hasActiveFilters, setTotalResults, setIsLoading, } = useProductFilters(); const { stats: storageStats, refreshStats: refreshStorageStats } = useStorageMonitor(); const { cacheOnly } = useCacheOnlyMode(); useEffect(() => { const detectCountry = async () => { try { const country = await detectUserCountry(true); setDetectedCountry(country.code); } catch { setDetectedCountry("br"); } }; detectCountry(); }, []); const loadProducts = useCallback( async (forceRefresh: boolean = false) => { try { setLoading(true); setError(null); setIsLoading(true); setCacheLoading(true); const countryToUse = detectedCountry || countryParam || "br"; if (!forceRefresh) { const cached = getCachedSearch( query, countryToUse, convertFiltersToRecord(filters) ); if (cached) { setAllProducts(cached.products); setTotalResults(cached.totalCount); setLoading(false); setIsLoading(false); setCacheLoading(false); return; } } const params = new URLSearchParams({ q: query, country: countryToUse, page: "1", page_size: "1000", }); if (cacheOnly) params.append("cache_only", "true"); const response = await fetch(`/api/search?${params.toString()}`); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "Erro ao buscar produtos"); } const result = { products: data.products || [], totalCount: data.count || 0, actualPages: 1, }; saveSearch( query, countryToUse, result.products, result.totalCount, convertFiltersToRecord(filters) ); setAllProducts(result.products); setTotalResults(result.totalCount); setCurrentPage(1); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Erro ao buscar produtos"; setError(errorMessage); setCacheError(errorMessage); } finally { setLoading(false); setIsLoading(false); setCacheLoading(false); } }, [ query, detectedCountry, countryParam, filters, cacheOnly, getCachedSearch, saveSearch, setIsLoading, setTotalResults, setCacheLoading, setCacheError, ] ); useEffect(() => { if (query) { loadProducts(forceRefresh); setForceRefresh(false); } }, [query, countryParam, detectedCountry, loadProducts, forceRefresh]); useEffect(() => { if (allProducts.length > 0) { fetch("/api/cache/sync", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ products: allProducts }), }).catch((err) => console.error("Erro ao sincronizar cache:", err)); } }, [allProducts]); const filteredAndSortedProducts = useMemo(() => { let filtered = [...allProducts]; if (gradeFilter) { filtered = filtered.filter( (p) => p.nutrition_grades?.toLowerCase() === gradeFilter ); } if (allergenFree) { filtered = filtered.filter( (p) => !p.allergens_tags || p.allergens_tags.length === 0 ); } const activeSortBy = filters.sortBy || "relevance"; const activeSortOrder = filters.sortOrder || "asc"; filtered = sortProducts(filtered, activeSortBy, activeSortOrder); return filtered; }, [allProducts, filters.sortBy, filters.sortOrder, gradeFilter, allergenFree]); const currentPageProducts = useMemo(() => { const startIndex = (currentPage - 1) * itemsPerPage; return filteredAndSortedProducts.slice(startIndex, startIndex + itemsPerPage); }, [filteredAndSortedProducts, currentPage, itemsPerPage]); const totalPages = Math.ceil(filteredAndSortedProducts.length / itemsPerPage); const totalItems = filteredAndSortedProducts.length; const handleRefresh = useCallback(() => setForceRefresh(true), []); const handleClearCache = useCallback(() => { clearCache( query, detectedCountry || countryParam || "br", convertFiltersToRecord(filters) ); setForceRefresh(true); }, [clearCache, query, detectedCountry, countryParam, filters]); const handleSortChange = useCallback( (sortBy: string) => { updateFilters({ sortBy: sortBy as | "relevance" | "nutrition_grade" | "name" | "brand" | "energy" | "fat" | "sugars" | "completeness" | "with_images" | "with_nutrition" | "eco_score" | "nova_group", }); }, [updateFilters] ); if (!query) { return (

Nenhuma busca realizada

Digite um termo para encontrar produtos.

); } return (
{/* ── Mobile top bar ──────────────────────────────────────────────── */}
{/* ── Desktop header ───────────────────────────────────────────────── */}
Voltar
{!loading && totalItems > 0 ? `${totalItems} resultado${totalItems !== 1 ? "s" : ""}` : "Buscando…"} {!loading && detectedCountry && ` · ${detectedCountry.toUpperCase()}`} {!loading && cacheState.isCached && ( Cache )}

{query ? ( <> Para{" "} “{query}” ) : ( "Todos os produtos" )}

{/* ── Full-width content container ─────────────────────────────────── */}
{/* ── Inline filter strip ────────────────────────────────────────── */} {!loading && totalItems > 0 && (
Filtrar {/* Grade pills A–E */}
{["a", "b", "c", "d", "e"].map((g) => { const active = gradeFilter === g; const col = GRADE_COLORS[g]; return ( ); })}
{/* Allergen-free chip */} {/* ── Mais Filtros button ─────────────────────────────────── */} {/* Sort + View toggle — pushed to the right */}
Ordenar {/* ── View mode toggle (inline, all breakpoints) ─────────── */}
{( [ { id: "grid" as const, Icon: LayoutGrid, label: "Cards" }, { id: "list" as const, Icon: List, label: "Lista" }, ] as const ).map(({ id, Icon, label }) => ( ))}
)} {/* ── Loading skeletons ──────────────────────────────────────────── */} {loading && ( viewMode === "grid" ? (
{[...Array(8)].map((_, i) => ( ))}
) : (
{[...Array(6)].map((_, i) => ( ))}
) )} {/* ── Error ─────────────────────────────────────────────────────── */} {error && !loading && (

Erro ao buscar produtos

{error}

)} {/* ── Empty state ────────────────────────────────────────────────── */} {!loading && !error && totalItems === 0 && (
Nada encontrado
Tente limpar os filtros ou usar outros termos.
{(hasActiveFilters || gradeFilter || allergenFree) && ( )}
)} {/* ── Product results ─────────────────────────────────────────────── */} {!loading && totalItems > 0 && ( <> {viewMode === "grid" ? ( /* ── Grid view ──────────────────────────────────────────────── */
{currentPageProducts.map((product, index) => (
))}
) : ( /* ── List view ──────────────────────────────────────────────── */
{currentPageProducts.map((product, index) => (
))}
)} {/* Pagination */} {totalPages > 1 && (
)} {/* Footer note */}
{cacheState.isCached ? "Dados do cache local" : "Dados da API Open Food Facts"} ·{" "}
)}
{/* ── Advanced filter sheet (right drawer) ──────────────────────────── */} Filtros avançados
{ updateFilters(newFilters); setCurrentPage(1); }} onClearFilters={() => { clearFilters(); setCurrentPage(1); }} isLoading={loading} totalResults={totalItems} sortBy={filters.sortBy || "relevance"} onSortChange={handleSortChange} onRefresh={handleRefresh} onClearCache={handleClearCache} isCached={cacheState.isCached} />
{/* ── Mobile sort sheet ──────────────────────────────────────────────── */}

Ordenar por

{[ { value: "relevance", label: "Relevância" }, { value: "nutrition_grade", label: "Nutri‑Score" }, { value: "name", label: "Nome (A–Z)" }, { value: "energy", label: "Energia" }, { value: "with_images", label: "Com Imagens" }, ].map((option) => ( ))}
{/* ── Cache info sheet ───────────────────────────────────────────────── */} Informações do Cache

Produtos em cache:

{storageStats?.totalProducts ?? 0}

); } export default function ResultadosPage() { return ( } > ); }