feat: adicionar busca dinâmica de projetos no header com dropdown

This commit is contained in:
Erik
2025-12-01 16:13:02 -03:00
parent 565aae1b9f
commit bee1af01ec
2 changed files with 155 additions and 2 deletions

View File

@@ -6,6 +6,7 @@ import { useState, useEffect } from 'react';
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { useLocale } from '@/contexts/LocaleContext'; import { useLocale } from '@/contexts/LocaleContext';
import { localeFlags, localeNames, type Locale } from '@/lib/i18n'; import { localeFlags, localeNames, type Locale } from '@/lib/i18n';
import SearchDropdown from './SearchDropdown';
export default function Header() { export default function Header() {
const [isSearchOpen, setIsSearchOpen] = useState(false); const [isSearchOpen, setIsSearchOpen] = useState(false);
@@ -132,8 +133,12 @@ export default function Header() {
</Link> </Link>
<div className="hidden md:flex items-center gap-4"> <div className="hidden md:flex items-center gap-4">
{/* Search Bar */} {/* Search Bar with Dropdown */}
<div className={`flex items-center bg-gray-100 dark:bg-white/10 rounded-full transition-all duration-300 ${isSearchOpen ? 'w-64 px-4 py-2' : 'w-10 h-10 justify-center cursor-pointer hover:bg-gray-200 dark:hover:bg-white/20'}`} onClick={() => !isSearchOpen && setIsSearchOpen(true)}> <div className="relative">
<div
className={`flex items-center bg-gray-100 dark:bg-white/10 rounded-full transition-all duration-300 ${isSearchOpen ? 'w-64 px-4 py-2' : 'w-10 h-10 justify-center cursor-pointer hover:bg-gray-200 dark:hover:bg-white/20'}`}
onClick={() => !isSearchOpen && setIsSearchOpen(true)}
>
<i className={`ri-search-line text-gray-500 dark:text-gray-300 ${isSearchOpen ? 'mr-2' : 'text-lg'}`}></i> <i className={`ri-search-line text-gray-500 dark:text-gray-300 ${isSearchOpen ? 'mr-2' : 'text-lg'}`}></i>
{isSearchOpen && ( {isSearchOpen && (
<input <input
@@ -144,6 +149,8 @@ export default function Header() {
className="bg-transparent border-none outline-none text-sm w-full text-gray-600 dark:text-gray-200 placeholder-gray-400" className="bg-transparent border-none outline-none text-sm w-full text-gray-600 dark:text-gray-200 placeholder-gray-400"
/> />
)} )}
</div>
<SearchDropdown isOpen={isSearchOpen} onClose={() => setIsSearchOpen(false)} />
</div> </div>
<nav className="flex items-center gap-6 mr-4"> <nav className="flex items-center gap-6 mr-4">

View File

@@ -0,0 +1,146 @@
"use client";
import { useState, useEffect } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { useLocale } from '@/contexts/LocaleContext';
interface Project {
id: string;
title: string;
coverImage: string | null;
category?: string;
}
interface SearchDropdownProps {
onClose?: () => void;
isOpen: boolean;
}
export default function SearchDropdown({ onClose, isOpen }: SearchDropdownProps) {
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState<Project[]>([]);
const [allProjects, setAllProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(false);
const { locale } = useLocale();
// Prefixo para links baseado no locale
const prefix = locale === 'pt' ? '' : `/${locale}`;
// Carregar todos os projetos uma vez
useEffect(() => {
async function fetchProjects() {
try {
const res = await fetch('/api/projects', { cache: 'no-store' });
if (res.ok) {
const data = await res.json();
setAllProjects(data);
}
} catch (error) {
console.error('Erro ao carregar projetos:', error);
}
}
if (isOpen) {
fetchProjects();
}
}, [isOpen]);
// Filtrar resultados conforme o usuário digita
useEffect(() => {
if (!searchTerm.trim()) {
setResults([]);
return;
}
setLoading(true);
// Simular pequeno delay para evitar renderizações desnecessárias
const timer = setTimeout(() => {
const filtered = allProjects.filter(project =>
project.title.toLowerCase().includes(searchTerm.toLowerCase())
);
setResults(filtered.slice(0, 5)); // Limitar a 5 resultados
setLoading(false);
}, 300);
return () => clearTimeout(timer);
}, [searchTerm, allProjects]);
const defaultImage = 'https://images.unsplash.com/photo-1504307651254-35680f356dfd?q=80&w=200&auto=format&fit=crop';
return (
<>
{/* Dropdown Overlay */}
{isOpen && searchTerm && (
<div className="absolute top-full left-0 right-0 mt-2 bg-white dark:bg-secondary rounded-lg shadow-xl border border-gray-200 dark:border-white/10 z-50 max-w-md w-full">
{loading && (
<div className="p-6 text-center">
<div className="animate-spin inline-block">
<i className="ri-loader-4-line text-2xl text-primary"></i>
</div>
</div>
)}
{!loading && results.length === 0 && searchTerm && (
<div className="p-6 text-center text-gray-500 dark:text-gray-400">
<i className="ri-search-line text-3xl mb-3 block opacity-50"></i>
<p>Nenhum projeto encontrado com "{searchTerm}"</p>
</div>
)}
{!loading && results.length > 0 && (
<div className="max-h-96 overflow-y-auto">
{results.map((project) => (
<Link
key={project.id}
href={`${prefix}/projetos`}
onClick={() => {
setSearchTerm('');
onClose?.();
}}
className="flex items-center gap-3 p-3 hover:bg-gray-50 dark:hover:bg-white/5 border-b border-gray-100 dark:border-white/10 last:border-b-0 transition-colors group"
>
<div className="relative w-16 h-16 flex-shrink-0 rounded-lg overflow-hidden bg-gray-200 dark:bg-white/5">
<Image
src={project.coverImage || defaultImage}
alt={project.title}
fill
className="object-cover group-hover:scale-110 transition-transform duration-300"
sizes="64px"
/>
</div>
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-gray-900 dark:text-white truncate group-hover:text-primary transition-colors">
{project.title}
</h3>
{project.category && (
<p className="text-xs text-gray-500 dark:text-gray-400 truncate">
{project.category}
</p>
)}
</div>
<i className="ri-arrow-right-line text-gray-400 group-hover:text-primary transition-colors flex-shrink-0 opacity-0 group-hover:opacity-100"></i>
</Link>
))}
{results.length > 0 && (
<Link
href={`${prefix}/projetos`}
onClick={() => {
setSearchTerm('');
onClose?.();
}}
className="flex items-center justify-center gap-2 p-3 text-primary font-medium hover:bg-primary/10 border-t border-gray-100 dark:border-white/10 transition-colors group"
>
<span>Ver todos os projetos</span>
<i className="ri-arrow-right-line group-hover:translate-x-1 transition-transform"></i>
</Link>
)}
</div>
)}
</div>
)}
</>
);
}