Barra de Pesquisa
This commit is contained in:
parent
e31d7f7046
commit
12aa0e34e1
@ -6,7 +6,8 @@ import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react"
|
||||
import { Input } from "@/components/ui/input" // <--- 1. Importação adicionada
|
||||
import { Edit, Trash2, Eye, Calendar, Filter, Loader2, Search } from "lucide-react" // <--- Adicionado ícone Search
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"
|
||||
|
||||
import { doctorsService } from "services/doctorsApi.mjs";
|
||||
@ -56,6 +57,7 @@ export default function DoctorsPage() {
|
||||
const [doctorToDeleteId, setDoctorToDeleteId] = useState<number | null>(null);
|
||||
|
||||
// --- Estados para Filtros ---
|
||||
const [searchTerm, setSearchTerm] = useState(""); // <--- 2. Novo estado para a busca
|
||||
const [specialtyFilter, setSpecialtyFilter] = useState("all");
|
||||
const [statusFilter, setStatusFilter] = useState("all");
|
||||
|
||||
@ -129,10 +131,21 @@ export default function DoctorsPage() {
|
||||
return [...new Set(specialties)];
|
||||
}, [doctors]);
|
||||
|
||||
// --- 3. Atualização da Lógica de Filtragem ---
|
||||
const filteredDoctors = doctors.filter((doctor) => {
|
||||
const specialtyMatch = specialtyFilter === "all" || doctor.specialty === specialtyFilter;
|
||||
const statusMatch = statusFilter === "all" || doctor.status === statusFilter;
|
||||
return specialtyMatch && statusMatch;
|
||||
|
||||
// Lógica da barra de pesquisa
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const nameMatch = doctor.full_name?.toLowerCase().includes(searchLower);
|
||||
const phoneMatch = doctor.phone_mobile?.includes(searchLower);
|
||||
// Opcional: buscar também por CRM se desejar
|
||||
const crmMatch = doctor.crm?.toLowerCase().includes(searchLower);
|
||||
|
||||
const searchMatch = searchTerm === "" || nameMatch || phoneMatch || crmMatch;
|
||||
|
||||
return specialtyMatch && statusMatch && searchMatch;
|
||||
});
|
||||
|
||||
const totalPages = Math.ceil(filteredDoctors.length / itemsPerPage);
|
||||
@ -189,55 +202,64 @@ export default function DoctorsPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filtros e Itens por Página */}
|
||||
<div className="flex flex-wrap items-center gap-3 bg-white p-3 sm:p-4 rounded-lg border border-gray-200">
|
||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||
<span className="text-sm font-medium text-foreground">Especialidade</span>
|
||||
<Select value={specialtyFilter} onValueChange={setSpecialtyFilter}>
|
||||
<SelectTrigger className="w-[160px] sm:w-[180px]">
|
||||
<SelectValue placeholder="Especialidade" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Todas</SelectItem>
|
||||
{uniqueSpecialties.map((specialty) => (
|
||||
<SelectItem key={specialty} value={specialty}>
|
||||
{specialty}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{/* --- Filtros e Barra de Pesquisa Atualizada --- */}
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center gap-3 bg-white p-3 sm:p-4 rounded-lg border border-gray-200">
|
||||
|
||||
{/* Barra de Pesquisa (Estilo similar à foto) */}
|
||||
<div className="relative w-full md:flex-1">
|
||||
<Filter className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="Buscar por nome ou telefone..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 w-full bg-gray-50 border-gray-200 focus:bg-white transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||
<span className="text-sm font-medium text-foreground">Status</span>
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="w-[160px] sm:w-[180px]">
|
||||
<SelectValue placeholder="Status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Todos</SelectItem>
|
||||
<SelectItem value="Ativo">Ativo</SelectItem>
|
||||
<SelectItem value="Férias">Férias</SelectItem>
|
||||
<SelectItem value="Inativo">Inativo</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3 w-full md:w-auto">
|
||||
<div className="flex items-center gap-2 w-full sm:w-auto">
|
||||
<Select value={specialtyFilter} onValueChange={setSpecialtyFilter}>
|
||||
<SelectTrigger className="w-full sm:w-[180px]">
|
||||
<SelectValue placeholder="Especialidade" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Todas Especialidades</SelectItem>
|
||||
{uniqueSpecialties.map((specialty) => (
|
||||
<SelectItem key={specialty} value={specialty}>
|
||||
{specialty}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 w-full sm:w-auto">
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="w-full sm:w-[150px]">
|
||||
<SelectValue placeholder="Status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Status: Todos</SelectItem>
|
||||
<SelectItem value="Ativo">Ativo</SelectItem>
|
||||
<SelectItem value="Férias">Férias</SelectItem>
|
||||
<SelectItem value="Inativo">Inativo</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="hidden lg:block">
|
||||
<Select onValueChange={handleItemsPerPageChange} defaultValue={String(itemsPerPage)}>
|
||||
<SelectTrigger className="w-[70px]">
|
||||
<SelectValue placeholder="10" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="5">5</SelectItem>
|
||||
<SelectItem value="10">10</SelectItem>
|
||||
<SelectItem value="20">20</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||
<span className="text-sm font-medium text-foreground">Itens por página</span>
|
||||
<Select onValueChange={handleItemsPerPageChange} defaultValue={String(itemsPerPage)}>
|
||||
<SelectTrigger className="w-[140px]">
|
||||
<SelectValue placeholder="Itens por pág." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="5">5 por página</SelectItem>
|
||||
<SelectItem value="10">10 por página</SelectItem>
|
||||
<SelectItem value="20">20 por página</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Button variant="outline" className="ml-auto w-full md:w-auto">
|
||||
<Filter className="w-4 h-4 mr-2" />
|
||||
Filtro avançado
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Tabela de Médicos (Visível em Telas Médias e Maiores) */}
|
||||
@ -272,10 +294,20 @@ export default function DoctorsPage() {
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{currentItems.map((doctor) => (
|
||||
<tr key={doctor.id} className="hover:bg-gray-50 transition">
|
||||
<td className="px-4 py-3 font-medium text-gray-900">{doctor.full_name}</td>
|
||||
<td className="px-4 py-3 font-medium text-gray-900">
|
||||
{doctor.full_name}
|
||||
<div className="text-xs text-gray-400 md:hidden">{doctor.phone_mobile}</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-gray-500 hidden sm:table-cell">{doctor.crm}</td>
|
||||
<td className="px-4 py-3 text-gray-500 hidden md:table-cell">{doctor.specialty}</td>
|
||||
<td className="px-4 py-3 text-gray-500 hidden lg:table-cell">{doctor.status || "N/A"}</td>
|
||||
<td className="px-4 py-3 text-gray-500 hidden lg:table-cell">
|
||||
<span className={`px-2 py-1 rounded-full text-xs ${
|
||||
doctor.status === 'Ativo' ? 'bg-green-100 text-green-800' :
|
||||
doctor.status === 'Inativo' ? 'bg-red-100 text-red-800' : 'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{doctor.status || "N/A"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-gray-500 hidden xl:table-cell">
|
||||
{(doctor.city || doctor.state)
|
||||
? `${doctor.city || ""}${doctor.city && doctor.state ? '/' : ''}${doctor.state || ""}`
|
||||
@ -284,7 +316,7 @@ export default function DoctorsPage() {
|
||||
<td className="px-4 py-3 text-right">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className="text-blue-600 cursor-pointer inline-block">Ações</div>
|
||||
<div className="text-blue-600 cursor-pointer inline-block hover:underline">Ações</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => openDetailsDialog(doctor)}>
|
||||
@ -335,14 +367,26 @@ export default function DoctorsPage() {
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{currentItems.map((doctor) => (
|
||||
<div key={doctor.id} className="bg-white-50 rounded-lg p-4 flex justify-between items-center border border-white-200">
|
||||
<div key={doctor.id} className="bg-gray-50 rounded-lg p-4 flex justify-between items-center border border-gray-100">
|
||||
<div>
|
||||
<div className="font-semibold text-gray-900">{doctor.full_name}</div>
|
||||
<div className="text-xs text-gray-500 mb-1">{doctor.phone_mobile}</div>
|
||||
<div className="text-sm text-gray-600">{doctor.specialty}</div>
|
||||
<div className="text-xs mt-1">
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs ${
|
||||
doctor.status === 'Ativo' ? 'bg-green-100 text-green-800' :
|
||||
doctor.status === 'Inativo' ? 'bg-red-100 text-red-800' : 'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{doctor.status || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className="text-blue-600 cursor-pointer inline-block">Ações</div>
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Abrir menu</span>
|
||||
<div className="font-bold text-gray-500">...</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => openDetailsDialog(doctor)}>
|
||||
@ -355,10 +399,6 @@ export default function DoctorsPage() {
|
||||
Editar
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Calendar className="mr-2 h-4 w-4" />
|
||||
Marcar consulta
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-600" onClick={() => openDeleteDialog(doctor.id)}>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Excluir
|
||||
@ -406,7 +446,7 @@ export default function DoctorsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Dialogs de Exclusão e Detalhes */}
|
||||
{/* Dialogs (Exclusão e Detalhes) mantidos igual ao original... */}
|
||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
|
||||
@ -4,7 +4,8 @@ import React, { useEffect, useState, useCallback } from "react";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Plus, Eye, Filter, Loader2 } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input"; // <--- 1. Importação Adicionada
|
||||
import { Plus, Eye, Filter, Loader2, Search } from "lucide-react"; // <--- 1. Ícone Search Adicionado
|
||||
import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||
import { api, login } from "services/api.mjs";
|
||||
import { usersService } from "services/usersApi.mjs";
|
||||
@ -34,6 +35,9 @@ export default function UsersPage() {
|
||||
const [userDetails, setUserDetails] = useState<UserInfoResponse | null>(
|
||||
null
|
||||
);
|
||||
|
||||
// --- Estados de Filtro ---
|
||||
const [searchTerm, setSearchTerm] = useState(""); // <--- 2. Estado da busca
|
||||
const [selectedRole, setSelectedRole] = useState<string>("all");
|
||||
|
||||
// --- Lógica de Paginação INÍCIO ---
|
||||
@ -118,10 +122,21 @@ export default function UsersPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const filteredUsers =
|
||||
selectedRole && selectedRole !== "all"
|
||||
? users.filter((u) => u.role === selectedRole)
|
||||
: users;
|
||||
// --- 3. Lógica de Filtragem Atualizada ---
|
||||
const filteredUsers = users.filter((u) => {
|
||||
// Filtro por Papel (Role)
|
||||
const roleMatch = selectedRole === "all" || u.role === selectedRole;
|
||||
|
||||
// Filtro da Barra de Pesquisa (Nome, Email ou Telefone)
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const nameMatch = u.full_name?.toLowerCase().includes(searchLower);
|
||||
const emailMatch = u.email?.toLowerCase().includes(searchLower);
|
||||
const phoneMatch = u.phone?.includes(searchLower);
|
||||
|
||||
const searchMatch = !searchTerm || nameMatch || emailMatch || phoneMatch;
|
||||
|
||||
return roleMatch && searchMatch;
|
||||
});
|
||||
|
||||
const indexOfLastItem = currentPage * itemsPerPage;
|
||||
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
||||
@ -180,60 +195,71 @@ export default function UsersPage() {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Filtro e Itens por Página */}
|
||||
<div className="flex flex-wrap items-center gap-3 bg-white p-4 rounded-lg border border-gray-200">
|
||||
{/* --- 4. Filtro (Barra de Pesquisa + Selects) --- */}
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center gap-3 bg-white p-4 rounded-lg border border-gray-200">
|
||||
|
||||
{/* Select de Filtro por Papel - Ajustado para resetar a página */}
|
||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||
<span className="text-sm font-medium text-foreground whitespace-nowrap">
|
||||
Filtrar por papel
|
||||
</span>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
setSelectedRole(value);
|
||||
setCurrentPage(1);
|
||||
{/* Barra de Pesquisa */}
|
||||
<div className="relative w-full md:flex-1">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="Buscar por nome, e-mail ou telefone..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
setCurrentPage(1); // Reseta a paginação ao pesquisar
|
||||
}}
|
||||
value={selectedRole}>
|
||||
|
||||
<SelectTrigger className="w-full sm:w-[180px]"> {/* w-full para mobile, w-[180px] para sm+ */}
|
||||
<SelectValue placeholder="Filtrar por papel" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Todos</SelectItem>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="gestor">Gestor</SelectItem>
|
||||
<SelectItem value="medico">Médico</SelectItem>
|
||||
<SelectItem value="secretaria">Secretária</SelectItem>
|
||||
<SelectItem value="user">Usuário</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
className="pl-10 w-full bg-gray-50 border-gray-200 focus:bg-white transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Select de Itens por Página */}
|
||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||
<span className="text-sm font-medium text-foreground whitespace-nowrap">
|
||||
Itens por página
|
||||
</span>
|
||||
<Select
|
||||
onValueChange={handleItemsPerPageChange}
|
||||
defaultValue={String(itemsPerPage)}
|
||||
>
|
||||
<SelectTrigger className="w-full sm:w-[140px]"> {/* w-full para mobile, w-[140px] para sm+ */}
|
||||
<SelectValue placeholder="Itens por pág." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="5">5 por página</SelectItem>
|
||||
<SelectItem value="10">10 por página</SelectItem>
|
||||
<SelectItem value="20">20 por página</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex flex-wrap items-center gap-3 w-full md:w-auto">
|
||||
{/* Select de Filtro por Papel */}
|
||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
setSelectedRole(value);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
value={selectedRole}>
|
||||
|
||||
<SelectTrigger className="w-full sm:w-[150px]">
|
||||
<SelectValue placeholder="Papel" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Todos</SelectItem>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="gestor">Gestor</SelectItem>
|
||||
<SelectItem value="medico">Médico</SelectItem>
|
||||
<SelectItem value="secretaria">Secretária</SelectItem>
|
||||
<SelectItem value="user">Usuário</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Select de Itens por Página */}
|
||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||
<Select
|
||||
onValueChange={handleItemsPerPageChange}
|
||||
defaultValue={String(itemsPerPage)}
|
||||
>
|
||||
<SelectTrigger className="w-full sm:w-[80px]">
|
||||
<SelectValue placeholder="10" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="5">5</SelectItem>
|
||||
<SelectItem value="10">10</SelectItem>
|
||||
<SelectItem value="20">20</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Button variant="outline" className="ml-auto w-full md:w-auto hidden lg:flex">
|
||||
<Filter className="w-4 h-4 mr-2" />
|
||||
Filtros
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="outline" className="ml-auto w-full md:w-auto">
|
||||
<Filter className="w-4 h-4 mr-2" />
|
||||
Filtro avançado
|
||||
</Button>
|
||||
</div>
|
||||
{/* Fim do Filtro e Itens por Página */}
|
||||
{/* Fim do Filtro */}
|
||||
|
||||
{/* Tabela/Lista */}
|
||||
<div className="bg-white rounded-lg border border-gray-200 shadow-md overflow-x-auto">
|
||||
@ -299,7 +325,10 @@ export default function UsersPage() {
|
||||
<div className="text-sm font-medium text-gray-900 truncate">
|
||||
{u.full_name || "—"}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 capitalize">
|
||||
<div className="text-xs text-gray-500 truncate">
|
||||
{u.email}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 capitalize mt-1">
|
||||
{u.role || "—"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user