feat: adicionar filtro de categoria e pesquisa na pagina de projetos
This commit is contained in:
@@ -2,59 +2,67 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { T } from "@/components/TranslatedText";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
title: string;
|
||||
category: string;
|
||||
location: string | null;
|
||||
description: string | null;
|
||||
coverImage: string | null;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export default function ProjetosPage() {
|
||||
// Placeholder data - will be replaced by database content
|
||||
const projects = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Adequação de Frota de Caminhões",
|
||||
category: "Engenharia Veicular",
|
||||
location: "Vitória, ES",
|
||||
image: "https://images.unsplash.com/photo-1616401784845-180882ba9ba8?q=80&w=2070&auto=format&fit=crop",
|
||||
description: "Projeto de adequação técnica de 50 caminhões para instalação de carrocerias especiais e sistemas de segurança."
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Laudo Técnico de Guindaste Industrial",
|
||||
category: "Laudos e Perícias",
|
||||
location: "Serra, ES",
|
||||
image: "https://images.unsplash.com/photo-1535082623926-b3a33d531740?q=80&w=2052&auto=format&fit=crop",
|
||||
description: "Inspeção completa e emissão de laudo técnico para guindaste de 45 toneladas, com testes de carga e verificação estrutural."
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Projeto de Equipamento Portuário",
|
||||
category: "Projetos Mecânicos",
|
||||
location: "Aracruz, ES",
|
||||
image: "https://images.unsplash.com/photo-1504917595217-d4dc5ebe6122?q=80&w=2070&auto=format&fit=crop",
|
||||
description: "Desenvolvimento e cálculo estrutural de Spreader para movimentação de contêineres em área portuária."
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Adequação NR-12 de Linha de Produção",
|
||||
category: "Segurança do Trabalho",
|
||||
location: "Linhares, ES",
|
||||
image: "https://images.unsplash.com/photo-1581092921461-eab62e97a782?q=80&w=2070&auto=format&fit=crop",
|
||||
description: "Inventário e adequação de segurança de 120 máquinas operatrizes conforme norma regulamentadora NR-12."
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "Homologação de Veículos Especiais",
|
||||
category: "Engenharia Veicular",
|
||||
location: "Viana, ES",
|
||||
image: "https://images.unsplash.com/photo-1591768793355-74d04bb6608f?q=80&w=2070&auto=format&fit=crop",
|
||||
description: "Processo completo de homologação e certificação de plataformas elevatórias para distribuição urbana."
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: "Sistema de Proteção Contra Quedas",
|
||||
category: "Segurança do Trabalho",
|
||||
location: "Cariacica, ES",
|
||||
image: "https://images.unsplash.com/photo-1504328345606-18bbc8c9d7d1?q=80&w=2070&auto=format&fit=crop",
|
||||
description: "Projeto e instalação de sistema de linha de vida para proteção contra quedas em operações de carga e descarga."
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchProjects() {
|
||||
try {
|
||||
const res = await fetch("/api/projects", { cache: "no-store" });
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
// Filtrar apenas projetos publicados
|
||||
const published = data.filter((p: Project) => p.status === "published");
|
||||
setProjects(published);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao carregar projetos:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
];
|
||||
fetchProjects();
|
||||
}, []);
|
||||
|
||||
// Extrair categorias únicas dos projetos
|
||||
const categories = useMemo(() => {
|
||||
const cats = new Set<string>();
|
||||
projects.forEach((p) => {
|
||||
if (p.category) cats.add(p.category);
|
||||
});
|
||||
return Array.from(cats);
|
||||
}, [projects]);
|
||||
|
||||
// Filtrar projetos por categoria e pesquisa
|
||||
const filteredProjects = useMemo(() => {
|
||||
return projects.filter((project) => {
|
||||
const matchesCategory = !selectedCategory || project.category === selectedCategory;
|
||||
const matchesSearch =
|
||||
!searchTerm ||
|
||||
project.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
project.description?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
project.category?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
project.location?.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
return matchesCategory && matchesSearch;
|
||||
});
|
||||
}, [projects, selectedCategory, searchTerm]);
|
||||
|
||||
const defaultImage = "https://images.unsplash.com/photo-1504307651254-35680f356dfd?q=80&w=2070&auto=format&fit=crop";
|
||||
|
||||
return (
|
||||
<main className="bg-white dark:bg-secondary transition-colors duration-300">
|
||||
@@ -73,40 +81,137 @@ export default function ProjetosPage() {
|
||||
{/* Projects Grid */}
|
||||
<section className="py-20 bg-white dark:bg-secondary">
|
||||
<div className="container mx-auto px-4">
|
||||
{/* Filters (Placeholder) */}
|
||||
<div className="flex flex-wrap gap-4 mb-12 justify-center">
|
||||
<button className="px-6 py-2 bg-primary text-white rounded-full font-bold shadow-md"><T>Todos</T></button>
|
||||
<button className="px-6 py-2 bg-gray-100 dark:bg-white/10 text-gray-600 dark:text-gray-300 rounded-full font-bold hover:bg-gray-200 dark:hover:bg-white/20 transition-colors"><T>Implementos</T></button>
|
||||
<button className="px-6 py-2 bg-gray-100 dark:bg-white/10 text-gray-600 dark:text-gray-300 rounded-full font-bold hover:bg-gray-200 dark:hover:bg-white/20 transition-colors"><T>Projetos Mecânicos</T></button>
|
||||
<button className="px-6 py-2 bg-gray-100 dark:bg-white/10 text-gray-600 dark:text-gray-300 rounded-full font-bold hover:bg-gray-200 dark:hover:bg-white/20 transition-colors"><T>Laudos</T></button>
|
||||
{/* Search Bar */}
|
||||
<div className="max-w-xl mx-auto mb-8">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Pesquisar projetos..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-5 py-3 pl-12 rounded-full border border-gray-200 dark:border-white/20 bg-white dark:bg-white/5 text-secondary dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
|
||||
/>
|
||||
<i className="ri-search-line absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 text-xl"></i>
|
||||
{searchTerm && (
|
||||
<button
|
||||
onClick={() => setSearchTerm("")}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-white transition-colors"
|
||||
>
|
||||
<i className="ri-close-line text-xl"></i>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{projects.map((project) => (
|
||||
<div key={project.id} className="group bg-white dark:bg-secondary rounded-xl overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-white/10 flex flex-col">
|
||||
<div className="relative h-64 overflow-hidden">
|
||||
<div className="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-110" style={{ backgroundImage: `url('${project.image}')` }}></div>
|
||||
<div className="absolute inset-0 bg-black/20 group-hover:bg-black/0 transition-colors"></div>
|
||||
<div className="absolute top-4 left-4 bg-white/90 backdrop-blur-sm px-3 py-1 rounded-md text-xs font-bold text-secondary uppercase tracking-wider">
|
||||
<T>{project.category}</T>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 grow flex flex-col">
|
||||
<h3 className="text-xl font-bold font-headline text-secondary dark:text-white mb-2 group-hover:text-primary transition-colors"><T>{project.title}</T></h3>
|
||||
<div className="flex items-center gap-2 text-gray-500 dark:text-gray-400 text-sm mb-4">
|
||||
<i className="ri-map-pin-line"></i>
|
||||
<span>{project.location}</span>
|
||||
</div>
|
||||
<p className="text-gray-600 dark:text-gray-400 text-sm mb-6 line-clamp-3 grow">
|
||||
<T>{project.description}</T>
|
||||
</p>
|
||||
<Link href={`/projetos/${project.id}`} className="inline-flex items-center gap-2 text-primary font-bold hover:gap-3 transition-all mt-auto">
|
||||
<T>Ver Detalhes</T> <i className="ri-arrow-right-line"></i>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/* Category Filters */}
|
||||
<div className="flex flex-wrap gap-3 mb-12 justify-center">
|
||||
<button
|
||||
onClick={() => setSelectedCategory(null)}
|
||||
className={`px-6 py-2 rounded-full font-bold transition-all ${
|
||||
selectedCategory === null
|
||||
? "bg-primary text-white shadow-md"
|
||||
: "bg-gray-100 dark:bg-white/10 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-white/20"
|
||||
}`}
|
||||
>
|
||||
<T>Todos</T>
|
||||
</button>
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
key={category}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
className={`px-6 py-2 rounded-full font-bold transition-all ${
|
||||
selectedCategory === category
|
||||
? "bg-primary text-white shadow-md"
|
||||
: "bg-gray-100 dark:bg-white/10 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-white/20"
|
||||
}`}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex justify-center items-center py-20">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Empty State */}
|
||||
{!loading && filteredProjects.length === 0 && (
|
||||
<div className="text-center py-20">
|
||||
<i className="ri-folder-open-line text-6xl text-gray-300 dark:text-gray-600 mb-4"></i>
|
||||
<h3 className="text-xl font-bold text-gray-500 dark:text-gray-400 mb-2">
|
||||
<T>Nenhum projeto encontrado</T>
|
||||
</h3>
|
||||
<p className="text-gray-400 dark:text-gray-500">
|
||||
{searchTerm || selectedCategory ? (
|
||||
<T>Tente ajustar os filtros de busca</T>
|
||||
) : (
|
||||
<T>Em breve adicionaremos novos projetos</T>
|
||||
)}
|
||||
</p>
|
||||
{(searchTerm || selectedCategory) && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchTerm("");
|
||||
setSelectedCategory(null);
|
||||
}}
|
||||
className="mt-4 px-6 py-2 bg-primary text-white rounded-full font-bold hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<T>Limpar filtros</T>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Projects Grid */}
|
||||
{!loading && filteredProjects.length > 0 && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{filteredProjects.map((project) => (
|
||||
<div key={project.id} className="group bg-white dark:bg-secondary rounded-xl overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-white/10 flex flex-col">
|
||||
<div className="relative h-64 overflow-hidden">
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-110"
|
||||
style={{ backgroundImage: `url('${project.coverImage || defaultImage}')` }}
|
||||
></div>
|
||||
<div className="absolute inset-0 bg-black/20 group-hover:bg-black/0 transition-colors"></div>
|
||||
{project.category && (
|
||||
<div className="absolute top-4 left-4 bg-white/90 backdrop-blur-sm px-3 py-1 rounded-md text-xs font-bold text-secondary uppercase tracking-wider">
|
||||
{project.category}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-6 grow flex flex-col">
|
||||
<h3 className="text-xl font-bold font-headline text-secondary dark:text-white mb-2 group-hover:text-primary transition-colors">
|
||||
{project.title}
|
||||
</h3>
|
||||
{project.location && (
|
||||
<div className="flex items-center gap-2 text-gray-500 dark:text-gray-400 text-sm mb-4">
|
||||
<i className="ri-map-pin-line"></i>
|
||||
<span>{project.location}</span>
|
||||
</div>
|
||||
)}
|
||||
{project.description && (
|
||||
<p className="text-gray-600 dark:text-gray-400 text-sm mb-6 line-clamp-3 grow">
|
||||
{project.description}
|
||||
</p>
|
||||
)}
|
||||
<Link href={`/projetos/${project.id}`} className="inline-flex items-center gap-2 text-primary font-bold hover:gap-3 transition-all mt-auto">
|
||||
<T>Ver Detalhes</T> <i className="ri-arrow-right-line"></i>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Results Count */}
|
||||
{!loading && filteredProjects.length > 0 && (
|
||||
<div className="text-center mt-12 text-gray-500 dark:text-gray-400">
|
||||
<T>Exibindo</T> {filteredProjects.length} <T>de</T> {projects.length} <T>projetos</T>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user