feat: adicionar busca dinâmica de projetos no header com dropdown
This commit is contained in:
@@ -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
|
||||||
@@ -145,6 +150,8 @@ export default function Header() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<SearchDropdown isOpen={isSearchOpen} onClose={() => setIsSearchOpen(false)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<nav className="flex items-center gap-6 mr-4">
|
<nav className="flex items-center gap-6 mr-4">
|
||||||
<Link href={`${prefix}/`} className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-primary font-medium transition-colors group">
|
<Link href={`${prefix}/`} className="flex items-center gap-2 text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-primary font-medium transition-colors group">
|
||||||
|
|||||||
146
frontend/src/components/SearchDropdown.tsx
Normal file
146
frontend/src/components/SearchDropdown.tsx
Normal 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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user