"use client"; import { useEffect, useState, useCallback } from "react"; import Link from "next/link"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Eye, Edit, Calendar, Trash2, Loader2, MoreVertical, Filter } from "lucide-react"; import { api } from "@/services/api.mjs"; import { PatientDetailsModal } from "@/components/ui/patient-details-modal"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import Sidebar from "@/components/Sidebar"; interface Paciente { id: string; nome: string; telefone: string; cidade: string; estado: string; ultimoAtendimento?: string; proximoAtendimento?: string; email?: string; birth_date?: string; cpf?: string; blood_type?: string; weight_kg?: number; height_m?: number; street?: string; number?: string; complement?: string; neighborhood?: string; cep?: string; // NOVOS CAMPOS PARA O FILTRO convenio?: string; vip?: string; } export default function PacientesPage() { const [pacientes, setPacientes] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedPatient, setSelectedPatient] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); // --- ESTADOS DOS FILTROS --- const [searchTerm, setSearchTerm] = useState(""); const [convenioFilter, setConvenioFilter] = useState("todos"); const [vipFilter, setVipFilter] = useState("todos"); // --- Lógica de Filtragem --- const filteredPacientes = pacientes.filter((p) => { // 1. Filtro de Texto (Nome ou Telefone) const searchLower = searchTerm.toLowerCase(); const matchesSearch = p.nome?.toLowerCase().includes(searchLower) || p.telefone?.includes(searchLower); // 2. Filtro de Convênio // Se for "todos", passa. Se não, verifica se o convênio do paciente é igual ao selecionado. const matchesConvenio = convenioFilter === "todos" || (p.convenio?.toLowerCase() === convenioFilter); // 3. Filtro VIP // Se for "todos", passa. Se não, verifica se o status VIP é igual ao selecionado. const matchesVip = vipFilter === "todos" || (p.vip?.toLowerCase() === vipFilter); return matchesSearch && matchesConvenio && matchesVip; }); // --- Lógica de Paginação --- const [itemsPerPage, setItemsPerPage] = useState(10); const [currentPage, setCurrentPage] = useState(1); // Resetar página quando qualquer filtro mudar useEffect(() => { setCurrentPage(1); }, [searchTerm, convenioFilter, vipFilter, itemsPerPage]); const totalPages = Math.ceil(filteredPacientes.length / itemsPerPage); const indexOfLastItem = currentPage * itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage; const currentItems = filteredPacientes.slice(indexOfFirstItem, indexOfLastItem); const paginate = (pageNumber: number) => setCurrentPage(pageNumber); const goToPrevPage = () => { setCurrentPage((prev) => Math.max(1, prev - 1)); }; const goToNextPage = () => { setCurrentPage((prev) => Math.min(totalPages, prev + 1)); }; const getVisiblePageNumbers = (totalPages: number, currentPage: number) => { const pages: number[] = []; const maxVisiblePages = 5; const halfRange = Math.floor(maxVisiblePages / 2); let startPage = Math.max(1, currentPage - halfRange); let endPage = Math.min(totalPages, currentPage + halfRange); if (endPage - startPage + 1 < maxVisiblePages) { if (endPage === totalPages) { startPage = Math.max(1, totalPages - maxVisiblePages + 1); } if (startPage === 1) { endPage = Math.min(totalPages, maxVisiblePages); } } for (let i = startPage; i <= endPage; i++) { pages.push(i); } return pages; }; const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage); const handleItemsPerPageChange = (value: string) => { setItemsPerPage(Number(value)); setCurrentPage(1); }; const handleOpenModal = (patient: Paciente) => { setSelectedPatient(patient); setIsModalOpen(true); }; const handleCloseModal = () => { setSelectedPatient(null); setIsModalOpen(false); }; const formatDate = (dateString: string | null | undefined) => { if (!dateString) return "N/A"; try { const date = new Date(dateString); return new Intl.DateTimeFormat("pt-BR").format(date); } catch (e) { return dateString; } }; const fetchPacientes = useCallback(async () => { try { setLoading(true); setError(null); const json = await api.get("/rest/v1/patients"); const items = Array.isArray(json) ? json : Array.isArray(json?.data) ? json.data : []; const mapped: Paciente[] = items.map((p: any) => ({ id: String(p.id ?? ""), nome: p.full_name ?? "—", telefone: p.phone_mobile ?? "N/A", cidade: p.city ?? "N/A", estado: p.state ?? "N/A", ultimoAtendimento: formatDate(p.created_at), proximoAtendimento: "N/A", email: p.email ?? "N/A", birth_date: p.birth_date ?? "N/A", cpf: p.cpf ?? "N/A", blood_type: p.blood_type ?? "N/A", weight_kg: p.weight_kg ?? 0, height_m: p.height_m ?? 0, street: p.street ?? "N/A", number: p.number ?? "N/A", complement: p.complement ?? "N/A", neighborhood: p.neighborhood ?? "N/A", cep: p.cep ?? "N/A", // ⚠️ ATENÇÃO: Verifique o nome real desses campos na sua API // Se a API não retorna, estou colocando valores padrão para teste convenio: p.insurance_plan || p.convenio || "Unimed", // Exemplo: mapeie o campo correto vip: p.is_vip ? "Sim" : "Não", // Exemplo: se for booleano converta para string })); setPacientes(mapped); } catch (e: any) { console.error("Erro ao carregar pacientes:", e); setError(e?.message || "Erro ao carregar pacientes"); } finally { setLoading(false); } }, []); useEffect(() => { fetchPacientes(); }, [fetchPacientes]); return (

Pacientes

Lista de pacientes vinculados

{/* --- BARRA DE PESQUISA COM FILTROS ATIVOS --- */}
{/* Input de Busca */}
setSearchTerm(e.target.value)} className="border-0 focus-visible:ring-0 shadow-none bg-transparent px-0 h-auto text-base placeholder:text-muted-foreground" />
{/* Filtros e Paginação */}
{/* FILTRO CONVÊNIO */}
Convênio
{/* FILTRO VIP */}
VIP
{/* PAGINAÇÃO */}
{/* Tabela de Dados */}
{/* Coluna Convênio visível para teste */} {loading ? ( ) : error ? ( ) : filteredPacientes.length === 0 ? ( ) : ( currentItems.map((p) => ( )) )}
Nome TelefoneConvênio VIP Último atendimento Ações
Carregando pacientes...
{`Erro: ${error}`}
Nenhum paciente encontrado com esses filtros.
{p.nome} {p.telefone} {p.convenio} {p.vip} {p.ultimoAtendimento} handleOpenModal(p)}> Ver detalhes Laudos
{/* Cards para Mobile */}
{loading ? (
Carregando...
) : filteredPacientes.length === 0 ? (
Nenhum paciente encontrado.
) : ( currentItems.map((p) => (
{p.nome || "—"}
{p.telefone} | {p.convenio} | VIP: {p.vip}
handleOpenModal(p)}> Ver detalhes Laudos
)) )}
{/* Paginação */} {totalPages > 1 && (
{visiblePageNumbers.map((number) => ( ))}
)}
); }