"use client"; import { useState, useEffect, useCallback } from "react"; import Link from "next/link"; 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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { patientsService } from "@/services/patientsApi.mjs"; import Sidebar from "@/components/Sidebar"; // Defina o tamanho da página. const PAGE_SIZE = 5; export default function PacientesPage() { // --- ESTADOS DE DADOS E GERAL --- const [searchTerm, setSearchTerm] = useState(""); const [convenioFilter, setConvenioFilter] = useState("all"); const [vipFilter, setVipFilter] = useState("all"); // Lista completa, carregada da API uma única vez const [allPatients, setAllPatients] = useState([]); // Lista após a aplicação dos filtros (base para a paginação) const [filteredPatients, setFilteredPatients] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // --- ESTADOS DE PAGINAÇÃO --- const [page, setPage] = useState(1); // CÁLCULO DA PAGINAÇÃO const totalPages = Math.ceil(filteredPatients.length / PAGE_SIZE); const startIndex = (page - 1) * PAGE_SIZE; const endIndex = startIndex + PAGE_SIZE; // Pacientes a serem exibidos na tabela (aplicando a paginação) const currentPatients = filteredPatients.slice(startIndex, endIndex); // --- ESTADOS DE DIALOGS --- const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [patientToDelete, setPatientToDelete] = useState(null); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); const [patientDetails, setPatientDetails] = useState(null); // --- FUNÇÕES DE LÓGICA --- // 1. Função para carregar TODOS os pacientes da API const fetchAllPacientes = useCallback(async () => { setLoading(true); setError(null); try { // Como o backend retorna um array, chamamos sem paginação const res = await patientsService.list(); const mapped = res.map((p: any) => ({ id: String(p.id ?? ""), nome: p.full_name ?? "—", telefone: p.phone_mobile ?? p.phone1 ?? "—", cidade: p.city ?? "—", estado: p.state ?? "—", // Formate as datas se necessário, aqui usamos como string ultimoAtendimento: p.last_visit_at?.split("T")[0] ?? "—", proximoAtendimento: p.next_appointment_at?.split("T")[0] ?? "—", vip: Boolean(p.vip ?? false), convenio: p.convenio ?? "Particular", // Define um valor padrão status: p.status ?? undefined, })); setAllPatients(mapped); } catch (e: any) { console.error(e); setError(e?.message || "Erro ao buscar pacientes"); } finally { setLoading(false); } }, []); // 2. Efeito para aplicar filtros e calcular a lista filtrada (chama-se quando allPatients ou filtros mudam) useEffect(() => { const filtered = allPatients.filter((patient) => { // Filtro por termo de busca (Nome ou Telefone) const matchesSearch = patient.nome?.toLowerCase().includes(searchTerm.toLowerCase()) || patient.telefone?.includes(searchTerm); // Filtro por Convênio const matchesConvenio = convenioFilter === "all" || patient.convenio === convenioFilter; // Filtro por VIP const matchesVip = vipFilter === "all" || (vipFilter === "vip" && patient.vip) || (vipFilter === "regular" && !patient.vip); return matchesSearch && matchesConvenio && matchesVip; }); setFilteredPatients(filtered); // Garante que a página atual seja válida após a filtragem setPage(1); }, [allPatients, searchTerm, convenioFilter, vipFilter]); // 3. Efeito inicial para buscar os pacientes useEffect(() => { fetchAllPacientes(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // --- LÓGICA DE AÇÕES (DELETAR / VER DETALHES) --- const openDetailsDialog = async (patientId: string) => { setDetailsDialogOpen(true); setPatientDetails(null); try { const res = await patientsService.getById(patientId); setPatientDetails(Array.isArray(res) ? res[0] : res); // Supondo que retorne um array com um item } catch (e: any) { setPatientDetails({ error: e?.message || "Erro ao buscar detalhes" }); } }; const handleDeletePatient = async (patientId: string) => { try { await patientsService.delete(patientId); // Atualiza a lista completa para refletir a exclusão setAllPatients((prev) => prev.filter((p) => String(p.id) !== String(patientId)) ); } catch (e: any) { alert(`Erro ao deletar paciente: ${e?.message || "Erro desconhecido"}`); } setDeleteDialogOpen(false); setPatientToDelete(null); }; const openDeleteDialog = (patientId: string) => { setPatientToDelete(patientId); setDeleteDialogOpen(true); }; return (
{/* Header (Responsividade OK) */}

Pacientes

Gerencie as informações de seus pacientes

{/* Bloco de Filtros (Responsividade APLICADA) */} {/* Adicionado flex-wrap para permitir que os itens quebrem para a linha de baixo */}
{/* Busca - Ocupa 100% no mobile, depois cresce */} setSearchTerm(e.target.value)} // w-full no mobile, depois flex-grow para ocupar o espaço disponível className="w-full sm:flex-grow sm:max-w-[300px] p-2 border rounded-md text-sm" /> {/* Convênio - Ocupa a largura total em telas pequenas, depois se ajusta */}
Convênio
{/* VIP - Ocupa a largura total em telas pequenas, depois se ajusta */}
VIP
{/* Aniversariantes - Ocupa 100% no mobile, e se alinha à direita no md+ */}
{/* --- SEÇÃO DE TABELA (VISÍVEL EM TELAS MAIORES OU IGUAIS A MD) --- */} {/* Garantir que a tabela se esconda em telas menores e apareça em MD+ */}
{" "} {/* Permite rolagem horizontal se a tabela for muito larga */} {error ? (
{`Erro ao carregar pacientes: ${error}`}
) : loading ? (
{" "} Carregando pacientes...
) : ( {" "} {/* min-w para evitar que a tabela se contraia demais */} {/* Ajustes de visibilidade de colunas para diferentes breakpoints */} {currentPatients.length === 0 ? ( ) : ( currentPatients.map((patient) => ( )) )}
Nome Telefone Cidade / Estado Convênio Último atendimento Próximo atendimento Ações
{allPatients.length === 0 ? "Nenhum paciente cadastrado" : "Nenhum paciente encontrado com os filtros aplicados"}
{patient.nome?.charAt(0) || "?"}
{patient.nome} {patient.vip && ( VIP )}
{patient.telefone} {`${patient.cidade} / ${patient.estado}`} {patient.convenio} {patient.ultimoAtendimento} {patient.proximoAtendimento}
Ações
openDetailsDialog(String(patient.id)) } > Ver detalhes Editar Marcar consulta openDeleteDialog(String(patient.id)) } > Excluir
)}
{/* --- SEÇÃO DE CARDS (VISÍVEL APENAS EM TELAS MENORES QUE MD) --- */} {/* Garantir que os cards apareçam em telas menores e se escondam em MD+ */}
{error ? (
{`Erro ao carregar pacientes: ${error}`}
) : loading ? (
{" "} Carregando pacientes...
) : filteredPatients.length === 0 ? (
{allPatients.length === 0 ? "Nenhum paciente cadastrado" : "Nenhum paciente encontrado com os filtros aplicados"}
) : (
{currentPatients.map((patient) => (
{patient.nome} {patient.vip && ( VIP )}
Telefone: {patient.telefone}
Convênio: {patient.convenio}
openDetailsDialog(String(patient.id))} > Ver detalhes Editar Marcar consulta openDeleteDialog(String(patient.id))} > Excluir
))}
)}
{/* Paginação */} {totalPages > 1 && !loading && (
{" "} {/* Adicionado flex-wrap e justify-center para botões da paginação */} {Array.from({ length: totalPages }, (_, index) => index + 1) .slice(Math.max(0, page - 3), Math.min(totalPages, page + 2)) .map((pageNumber) => ( ))}
)} {/* AlertDialogs (Permanecem os mesmos) */} Confirmar exclusão Tem certeza que deseja excluir este paciente? Esta ação não pode ser desfeita. Cancelar patientToDelete && handleDeletePatient(patientToDelete) } className="bg-red-600 hover:bg-red-700" > Excluir Detalhes do Paciente {patientDetails === null ? (
Carregando...
) : patientDetails?.error ? (
{patientDetails.error}
) : (

Nome Completo

{patientDetails.full_name}

Email

{patientDetails.email}

Telefone

{patientDetails.phone_mobile}

Data de Nascimento

{patientDetails.birth_date}

CPF

{patientDetails.cpf}

Tipo Sanguíneo

{patientDetails.blood_type}

Peso (kg)

{patientDetails.weight_kg}

Altura (m)

{patientDetails.height_m}

Endereço

Rua

{`${patientDetails.street}, ${patientDetails.number}`}

Complemento

{patientDetails.complement}

Bairro

{patientDetails.neighborhood}

Cidade

{patientDetails.cidade}

Estado

{patientDetails.estado}

CEP

{patientDetails.cep}

)}
Fechar
); }