"use client"; import { useState, useEffect, useRef, 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 { Plus, Edit, Trash2, Eye, Calendar, Filter } from "lucide-react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import ManagerLayout from "@/components/manager-layout"; import { patientsService } from "@/services/patientsApi.mjs"; // 📅 PASSO 1: Criar uma função para formatar a data const formatDate = (dateString: string | null | undefined): string => { // Se a data nĂŁo existir, retorna um texto padrĂŁo if (!dateString) { return "N/A"; } try { const date = new Date(dateString); // Verifica se a data Ă© vĂĄlida apĂłs a conversĂŁo if (isNaN(date.getTime())) { return "Data invĂĄlida"; } const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); // MĂȘs Ă© base 0, entĂŁo +1 const year = date.getFullYear(); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); return `${day}/${month}/${year} ${hours}:${minutes}`; } catch (error) { // Se houver qualquer erro na conversĂŁo, retorna um texto de erro return "Data invĂĄlida"; } }; export default function PacientesPage() { const [searchTerm, setSearchTerm] = useState(""); const [convenioFilter, setConvenioFilter] = useState("all"); const [vipFilter, setVipFilter] = useState("all"); const [patients, setPatients] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [page, setPage] = useState(1); const [hasNext, setHasNext] = useState(true); const [isFetching, setIsFetching] = useState(false); const observerRef = useRef(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [patientToDelete, setPatientToDelete] = useState(null); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); const [patientDetails, setPatientDetails] = useState(null); const openDetailsDialog = async (patientId: string) => { setDetailsDialogOpen(true); setPatientDetails(null); try { const res = await patientsService.getById(patientId); setPatientDetails(res[0]); } catch (e: any) { setPatientDetails({ error: e?.message || "Erro ao buscar detalhes" }); } }; const fetchPacientes = useCallback( async (pageToFetch: number) => { if (isFetching || !hasNext) return; setIsFetching(true); setError(null); try { 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 ?? "", ultimoAtendimento: p.last_visit_at ?? "", proximoAtendimento: p.next_appointment_at ?? "", vip: Boolean(p.vip ?? false), convenio: p.convenio ?? "", // se nĂŁo existir, fica vazio status: p.status ?? undefined, })); setPatients((prev) => { const all = [...prev, ...mapped]; const unique = Array.from( new Map(all.map((p) => [p.id, p])).values() ); return unique; }); if (!mapped.id) setHasNext(false); // parar carregamento else setPage((prev) => prev + 1); } catch (e: any) { setError(e?.message || "Erro ao buscar pacientes"); } finally { setIsFetching(false); } }, [isFetching, hasNext] ); useEffect(() => { fetchPacientes(page); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (!observerRef.current || !hasNext) return; const observer = new window.IntersectionObserver((entries) => { if (entries[0].isIntersecting && !isFetching && hasNext) { fetchPacientes(page); } }); observer.observe(observerRef.current); return () => { if (observerRef.current) observer.unobserve(observerRef.current); }; }, [fetchPacientes, page, hasNext, isFetching]); const handleDeletePatient = async (patientId: string) => { // Remove from current list (client-side deletion) try { const res = await patientsService.delete(patientId); if (res) { alert(`${res.error} ${res.message}`); } setPatients((prev) => prev.filter((p) => String(p.id) !== String(patientId)) ); } catch (e: any) { setError(e?.message || "Erro ao deletar paciente"); } setDeleteDialogOpen(false); setPatientToDelete(null); }; const openDeleteDialog = (patientId: string) => { setPatientToDelete(patientId); setDeleteDialogOpen(true); }; const filteredPatients = patients.filter((patient) => { const matchesSearch = patient.nome?.toLowerCase().includes(searchTerm.toLowerCase()) || patient.telefone?.includes(searchTerm); const matchesConvenio = convenioFilter === "all" || (patient.convenio ?? "") === convenioFilter; const matchesVip = vipFilter === "all" || (vipFilter === "vip" && patient.vip) || (vipFilter === "regular" && !patient.vip); return matchesSearch && matchesConvenio && matchesVip; }); return (

Pacientes

Gerencie as informaçÔes de seus pacientes

{/* ConvĂȘnio */}
ConvĂȘnio
VIP
Aniversariantes
{error ? (
{`Erro ao carregar pacientes: ${error}`}
) : ( {filteredPatients.length === 0 ? ( ) : ( filteredPatients.map((patient) => ( {/* 📅 PASSO 2: Aplicar a formatação de data na tabela */} )) )}
Nome Telefone Cidade Estado Último atendimento PrĂłximo atendimento AçÔes
{patients.length === 0 ? "Nenhum paciente cadastrado" : "Nenhum paciente encontrado com os filtros aplicados"}
{patient.nome?.charAt(0) || "?"}
{patient.nome}
{patient.telefone} {patient.cidade} {patient.estado}{formatDate(patient.ultimoAtendimento)} {formatDate(patient.proximoAtendimento)}
AçÔes
openDetailsDialog(String(patient.id))}> Ver detalhes Editar Marcar consulta openDeleteDialog(String(patient.id))}> Excluir
)}
{isFetching &&
Carregando mais pacientes...
}
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 {/* Modal de detalhes do paciente */} Detalhes do Paciente {patientDetails === null ? (
Carregando...
) : patientDetails?.error ? (
{patientDetails.error}
) : (

Nome: {patientDetails.full_name}

CPF: {patientDetails.cpf}

Email: {patientDetails.email}

Telefone: {patientDetails.phone_mobile ?? patientDetails.phone1 ?? patientDetails.phone2 ?? "-"}

Nome social: {patientDetails.social_name ?? "-"}

Sexo: {patientDetails.sex ?? "-"}

Tipo sanguĂ­neo: {patientDetails.blood_type ?? "-"}

Peso: {patientDetails.weight_kg ?? "-"} {patientDetails.weight_kg ? "kg" : ""}

Altura: {patientDetails.height_m ?? "-"} {patientDetails.height_m ? "m" : ""}

IMC: {patientDetails.bmi ?? "-"}

Endereço: {patientDetails.street ?? "-"}

Bairro: {patientDetails.neighborhood ?? "-"}

Cidade: {patientDetails.city ?? "-"}

Estado: {patientDetails.state ?? "-"}

CEP: {patientDetails.cep ?? "-"}

{/* 📅 PASSO 3: Aplicar a formatação de data no modal */}

Criado em: {formatDate(patientDetails.created_at)}

Atualizado em: {formatDate(patientDetails.updated_at)}

Id: {patientDetails.id ?? "-"}

)}
Fechar
); }