diff --git a/frontend/src/app/admin/projetos/page.tsx b/frontend/src/app/admin/projetos/page.tsx index 0012841..4402dcc 100644 --- a/frontend/src/app/admin/projetos/page.tsx +++ b/frontend/src/app/admin/projetos/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import Link from 'next/link'; import { useToast } from '@/contexts/ToastContext'; import { useConfirm } from '@/contexts/ConfirmContext'; @@ -29,6 +29,11 @@ const STATUS_STYLES: Record = { export default function ProjectsList() { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(''); + const [filterCategory, setFilterCategory] = useState(''); + const [filterStatus, setFilterStatus] = useState(''); + const [filterDateFrom, setFilterDateFrom] = useState(''); + const [filterDateTo, setFilterDateTo] = useState(''); const { success, error } = useToast(); const { confirm } = useConfirm(); @@ -54,6 +59,70 @@ export default function ProjectsList() { } }; + // Extrair categorias e status únicos + const categories = useMemo(() => { + const cats = new Set(); + projects.forEach((p) => { + if (p.category) cats.add(p.category); + }); + return Array.from(cats).sort(); + }, [projects]); + + const statuses = useMemo(() => { + const stats = new Set(); + projects.forEach((p) => { + if (p.status) stats.add(p.status); + }); + return Array.from(stats); + }, [projects]); + + // Filtrar projetos + const filteredProjects = useMemo(() => { + return projects.filter((project) => { + // Filtro de pesquisa + const matchesSearch = !searchTerm || + project.title.toLowerCase().includes(searchTerm.toLowerCase()) || + project.client?.toLowerCase().includes(searchTerm.toLowerCase()) || + project.category?.toLowerCase().includes(searchTerm.toLowerCase()) || + project.description?.toLowerCase().includes(searchTerm.toLowerCase()); + + // Filtro de categoria + const matchesCategory = !filterCategory || project.category === filterCategory; + + // Filtro de status + const matchesStatus = !filterStatus || project.status === filterStatus; + + // Filtro de data (criação) + let matchesDateFrom = true; + let matchesDateTo = true; + + if (filterDateFrom) { + const projectDate = new Date(project.createdAt); + const fromDate = new Date(filterDateFrom); + matchesDateFrom = projectDate >= fromDate; + } + + if (filterDateTo) { + const projectDate = new Date(project.createdAt); + const toDate = new Date(filterDateTo); + toDate.setHours(23, 59, 59, 999); + matchesDateTo = projectDate <= toDate; + } + + return matchesSearch && matchesCategory && matchesStatus && matchesDateFrom && matchesDateTo; + }); + }, [projects, searchTerm, filterCategory, filterStatus, filterDateFrom, filterDateTo]); + + const clearFilters = () => { + setSearchTerm(''); + setFilterCategory(''); + setFilterStatus(''); + setFilterDateFrom(''); + setFilterDateTo(''); + }; + + const hasActiveFilters = searchTerm || filterCategory || filterStatus || filterDateFrom || filterDateTo; + const handleDelete = async (id: string) => { const confirmed = await confirm({ title: 'Excluir projeto', @@ -116,15 +185,126 @@ export default function ProjectsList() { + {/* Filters Section */} +
+
+ {/* Search */} +
+
+ setSearchTerm(e.target.value)} + className="w-full px-4 py-2.5 pl-10 rounded-lg border border-gray-200 dark:border-white/10 bg-gray-50 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" + /> + + {searchTerm && ( + + )} +
+
+ + {/* Category Filter */} +
+ +
+ + {/* Status Filter */} +
+ +
+ + {/* Date From Filter */} +
+ setFilterDateFrom(e.target.value)} + className="w-full px-4 py-2.5 rounded-lg border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5 text-secondary dark:text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all cursor-pointer" + title="Data inicial" + /> +
+ + {/* Date To Filter */} +
+ setFilterDateTo(e.target.value)} + className="w-full px-4 py-2.5 rounded-lg border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5 text-secondary dark:text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all cursor-pointer" + title="Data final" + /> +
+ + {/* Clear Filters */} + {hasActiveFilters && ( + + )} +
+ + {/* Results count */} + {!loading && ( +
+ {hasActiveFilters ? ( + Exibindo {filteredProjects.length} de {projects.length} projetos + ) : ( + {projects.length} projeto{projects.length !== 1 ? 's' : ''} cadastrado{projects.length !== 1 ? 's' : ''} + )} +
+ )} +
+
{loading ? (
- ) : projects.length === 0 ? ( + ) : filteredProjects.length === 0 ? (
- Nenhum projeto cadastrado ainda. + {hasActiveFilters ? ( + <> + Nenhum projeto encontrado com os filtros aplicados. + + + ) : ( + 'Nenhum projeto cadastrado ainda.' + )}
) : (
@@ -134,13 +314,14 @@ export default function ProjectsList() { Projeto Categoria Cliente + Inserção Conclusão Status Ações - {projects.map((project) => ( + {filteredProjects.map((project) => (
@@ -160,12 +341,19 @@ export default function ProjectsList() { Destaque )}

-

Criado em {formatDate(project.createdAt)}

{project.category} {project.client || '—'} + +
+ {formatDate(project.createdAt)} + + {new Date(project.createdAt).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })} + +
+ {formatDate(project.completionDate)} {renderStatus(project.status)}