feat(search): implement paginated API fetching for large page sizes

- Removed upper limit on page_size validation to support requests >100
- Added automatic pagination logic to fetch from Open Food Facts API in chunks of 1000
- Accumulates products across multiple API pages until desired count is reached
- Updated error messages and product data fields for consistency
This commit is contained in:
Evandro Fonseca Junior committed 2025-10-09 07:16:26 -03:00
1 parent c173fec668
commit bfb58ef16e
34 files changed
+18630 -7815

No files matched your search

+79 -2
View File
@@ -1,27 +1,60 @@
"use client";
import { useState } from "react";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Search } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { detectSearchType, isValidBarcode } from "@/lib/openFoodFactsApi";
interface SearchBarProps {
initialQuery?: string;
className?: string;
placeholder?: string;
compact?: boolean; // Modo compacto para mobile
}
export function SearchBar({
initialQuery = "",
className = "",
placeholder = "Buscar produtos...",
compact = false,
}: SearchBarProps) {
const [query, setQuery] = useState(initialQuery);
const [searchType, setSearchType] = useState<"barcode" | "name" | null>(null);
const router = useRouter();
// Detectar tipo de busca em tempo real
useEffect(() => {
const cleaned = query.replace(/\D/g, ""); // Remove tudo que não é número
if (cleaned.length >= 8 && cleaned.length <= 13) {
setSearchType("barcode");
} else if (query.trim()) {
setSearchType("name");
} else {
setSearchType(null);
}
}, [query]);
const handleSearch = () => {
if (query.trim()) {
if (!query.trim()) return;
const type = detectSearchType(query);
if (type === "barcode") {
// Limpar código de barras (remover espaços/caracteres)
const cleanBarcode = query.replace(/\D/g, "");
if (isValidBarcode(cleanBarcode)) {
// Redirecionar para página do produto
router.push(`/produto/${cleanBarcode}`);
} else {
// Mostrar erro de código inválido
alert("Código de barras inválido. Deve ter entre 8 e 13 dígitos.");
}
} else {
// Busca normal por nome
router.push(`/resultados?q=${encodeURIComponent(query.trim())}`);
}
};
@@ -32,6 +65,41 @@ export function SearchBar({
}
};
if (compact) {
return (
<div className={`flex items-center gap-2 ${className}`}>
<div className="relative flex-1">
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
type="text"
placeholder={placeholder}
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyPress={handleKeyPress}
className="pl-8 pr-2 h-9 text-sm"
/>
{/* Indicador visual do tipo de busca - mais compacto */}
{searchType && (
<div className="absolute right-2 top-1/2 transform -translate-y-1/2">
<span className="text-xs text-gray-500 bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded text-[10px]">
{searchType === "barcode" ? "Código" : "Nome"}
</span>
</div>
)}
</div>
<Button
onClick={handleSearch}
disabled={!query.trim()}
size="sm"
className="h-9 px-3"
>
<Search className="h-4 w-4" />
</Button>
</div>
);
}
return (
<div className={`flex items-center gap-2 ${className}`}>
<div className="relative flex-1 max-w-md">
@@ -44,6 +112,15 @@ export function SearchBar({
onKeyPress={handleKeyPress}
className="pl-10 pr-4 h-10"
/>
{/* Indicador visual do tipo de busca */}
{searchType && (
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
<span className="text-xs text-gray-500 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">
{searchType === "barcode" ? "Código de barras" : "Nome"}
</span>
</div>
)}
</div>
<Button
onClick={handleSearch}