import { useState, useEffect } from "react"; import toast from "react-hot-toast"; import { Search, Plus, Eye, Calendar, Edit, Trash2, X } from "lucide-react"; import { patientService, type Patient } from "../../services"; import PacienteForm, { type PacienteFormData } from "../pacientes/PacienteForm"; import { Avatar } from "../ui/Avatar"; import { useAuth } from "../../hooks/useAuth"; const BLOOD_TYPES = ["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"]; const CONVENIOS = [ "Particular", "Unimed", "Amil", "Bradesco Saúde", "SulAmérica", "Golden Cross", ]; const COUNTRY_OPTIONS = [ { value: "55", label: "+55 🇧🇷 Brasil" }, { value: "1", label: "+1 🇺🇸 EUA/Canadá" }, ]; // Função para buscar endereço via CEP const buscarEnderecoViaCEP = async (cep: string) => { try { const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`); const data = await response.json(); if (data.erro) return null; return { rua: data.logradouro, bairro: data.bairro, cidade: data.localidade, estado: data.uf, cep: data.cep, }; } catch { return null; } }; export function SecretaryPatientList({ onOpenAppointment, }: { onOpenAppointment?: (patientId: string) => void; }) { const { user } = useAuth(); const [patients, setPatients] = useState([]); const [loading, setLoading] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [insuranceFilter, setInsuranceFilter] = useState("Todos"); const [showBirthdays, setShowBirthdays] = useState(false); const [showVIP, setShowVIP] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage] = useState(10); // Modal states const [showModal, setShowModal] = useState(false); const [modalMode, setModalMode] = useState<"create" | "edit">("create"); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [patientToDelete, setPatientToDelete] = useState(null); const [showViewModal, setShowViewModal] = useState(false); const [selectedPatient, setSelectedPatient] = useState(null); const [formData, setFormData] = useState({ nome: "", social_name: "", cpf: "", sexo: "", dataNascimento: "", email: "", codigoPais: "55", ddd: "", numeroTelefone: "", tipo_sanguineo: "", altura: "", peso: "", convenio: "Particular", numeroCarteirinha: "", observacoes: "", endereco: { cep: "", rua: "", numero: "", bairro: "", cidade: "", estado: "", }, }); const [cpfError, setCpfError] = useState(null); const [cpfValidationMessage, setCpfValidationMessage] = useState< string | null >(null); const loadPatients = async () => { setLoading(true); try { const data = await patientService.list(); console.log("✅ Pacientes carregados:", data); // Log para verificar se temos user_id if (Array.isArray(data) && data.length > 0) { console.log("📋 Primeiro paciente (verificar user_id):", { full_name: data[0].full_name, user_id: data[0].user_id, avatar_url: data[0].avatar_url, email: data[0].email, }); } setPatients(Array.isArray(data) ? data : []); if (Array.isArray(data) && data.length === 0) { console.warn("⚠️ Nenhum paciente encontrado na API"); } } catch (error) { console.error("❌ Erro ao carregar pacientes:", error); toast.error("Erro ao carregar pacientes"); setPatients([]); } finally { setLoading(false); } }; useEffect(() => { loadPatients(); }, []); // Função de filtro const filteredPatients = patients.filter((patient) => { // Filtro de busca por nome, CPF ou email const searchLower = searchTerm.toLowerCase(); const matchesSearch = !searchTerm || patient.full_name?.toLowerCase().includes(searchLower) || patient.cpf?.includes(searchTerm) || patient.email?.toLowerCase().includes(searchLower); // Filtro de aniversariantes do mês const matchesBirthday = !showBirthdays || (() => { if (!patient.birth_date) return false; const birthDate = new Date(patient.birth_date); const currentMonth = new Date().getMonth(); const birthMonth = birthDate.getMonth(); return currentMonth === birthMonth; })(); // Filtro de convênio const matchesInsurance = insuranceFilter === "Todos" || ((patient as any).convenio || "Particular") === insuranceFilter; // Filtro VIP (se o backend fornecer uma flag 'is_vip' ou 'vip') const matchesVIP = !showVIP || ((patient as any).is_vip === true || (patient as any).vip === true); return matchesSearch && matchesBirthday && matchesInsurance && matchesVIP; }); // Cálculos de paginação const totalPages = Math.ceil(filteredPatients.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const paginatedPatients = filteredPatients.slice(startIndex, endIndex); const handleSearch = () => { loadPatients(); }; const handleClear = () => { setSearchTerm(""); setInsuranceFilter("Todos"); setShowBirthdays(false); setShowVIP(false); setCurrentPage(1); loadPatients(); }; // Reset página quando filtros mudarem useEffect(() => { setCurrentPage(1); }, [searchTerm, insuranceFilter, showBirthdays, showVIP]); const handleNewPatient = () => { setModalMode("create"); setFormData({ nome: "", social_name: "", cpf: "", sexo: "", dataNascimento: "", email: "", codigoPais: "55", ddd: "", numeroTelefone: "", tipo_sanguineo: "", altura: "", peso: "", convenio: "Particular", numeroCarteirinha: "", observacoes: "", endereco: { cep: "", rua: "", numero: "", bairro: "", cidade: "", estado: "", }, }); setCpfError(null); setCpfValidationMessage(null); setShowModal(true); }; const handleEditPatient = (patient: Patient) => { setModalMode("edit"); setFormData({ id: patient.id, user_id: patient.user_id, nome: patient.full_name || "", social_name: patient.social_name || "", cpf: patient.cpf || "", sexo: patient.sex || "", dataNascimento: patient.birth_date || "", email: patient.email || "", codigoPais: "55", ddd: "", numeroTelefone: patient.phone_mobile || "", tipo_sanguineo: patient.blood_type || "", altura: patient.height_m?.toString() || "", peso: patient.weight_kg?.toString() || "", convenio: "Particular", numeroCarteirinha: "", observacoes: "", avatar_url: patient.avatar_url || undefined, endereco: { cep: patient.cep || "", rua: patient.street || "", numero: patient.number || "", complemento: patient.complement || "", bairro: patient.neighborhood || "", cidade: patient.city || "", estado: patient.state || "", }, }); setCpfError(null); setCpfValidationMessage(null); setShowModal(true); }; const handleFormChange = (patch: Partial) => { setFormData((prev) => ({ ...prev, ...patch })); }; const handleCpfChange = (value: string) => { setFormData((prev) => ({ ...prev, cpf: value })); setCpfError(null); setCpfValidationMessage(null); }; const handleCepLookup = async (cep: string) => { const endereco = await buscarEnderecoViaCEP(cep); if (endereco) { setFormData((prev) => ({ ...prev, endereco: { ...prev.endereco, ...endereco, }, })); toast.success("Endereço encontrado!"); } else { toast.error("CEP não encontrado"); } }; const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); try { if (modalMode === "edit" && formData.id) { // Para edição, usa o endpoint antigo (PATCH /patients/:id) // Remove formatação de telefone, CPF e CEP const cleanPhone = formData.numeroTelefone.replace(/\D/g, ""); const cleanCpf = formData.cpf.replace(/\D/g, ""); const cleanCep = formData.endereco.cep ? formData.endereco.cep.replace(/\D/g, "") : null; const patientData = { full_name: formData.nome, social_name: formData.social_name || null, cpf: cleanCpf, sex: formData.sexo || null, birth_date: formData.dataNascimento || null, email: formData.email, phone_mobile: cleanPhone, blood_type: formData.tipo_sanguineo || null, height_m: formData.altura ? parseFloat(formData.altura) : null, weight_kg: formData.peso ? parseFloat(formData.peso) : null, cep: cleanCep, street: formData.endereco.rua || null, number: formData.endereco.numero || null, complement: formData.endereco.complemento || null, neighborhood: formData.endereco.bairro || null, city: formData.endereco.cidade || null, state: formData.endereco.estado || null, }; await patientService.update(formData.id, patientData); toast.success("Paciente atualizado com sucesso!"); } else { // Criar novo paciente usando a API REST direta // Remove formatação de telefone e CPF const cleanPhone = formData.numeroTelefone.replace(/\D/g, ""); const cleanCpf = formData.cpf.replace(/\D/g, ""); const cleanCep = formData.endereco.cep ? formData.endereco.cep.replace(/\D/g, "") : null; const createData = { full_name: formData.nome, cpf: cleanCpf, email: formData.email, phone_mobile: cleanPhone, birth_date: formData.dataNascimento || null, social_name: formData.social_name || null, sex: formData.sexo || null, blood_type: formData.tipo_sanguineo || null, weight_kg: formData.peso ? parseFloat(formData.peso) : null, height_m: formData.altura ? parseFloat(formData.altura) : null, street: formData.endereco.rua || null, number: formData.endereco.numero || null, complement: formData.endereco.complemento || null, neighborhood: formData.endereco.bairro || null, city: formData.endereco.cidade || null, state: formData.endereco.estado || null, cep: cleanCep, created_by: user?.id || undefined, }; await patientService.create(createData); toast.success("Paciente cadastrado com sucesso!"); } setShowModal(false); loadPatients(); } catch (error) { console.error("Erro ao salvar paciente:", error); toast.error("Erro ao salvar paciente"); } finally { setLoading(false); } }; const handleCancelForm = () => { setShowModal(false); }; const handleDeleteClick = (patient: Patient) => { setPatientToDelete(patient); setShowDeleteDialog(true); }; const handleViewPatient = (patient: Patient) => { setSelectedPatient(patient); setShowViewModal(true); }; const handleSchedulePatient = (patient: Patient) => { if (onOpenAppointment) { onOpenAppointment(patient.id as string); } else { // fallback: store in sessionStorage and dispatch event sessionStorage.setItem("selectedPatientForAppointment", patient.id as string); window.dispatchEvent(new CustomEvent("open-create-appointment")); } }; const handleConfirmDelete = async () => { if (!patientToDelete?.id) return; setLoading(true); try { await patientService.delete(patientToDelete.id); toast.success("Paciente deletado com sucesso!"); setShowDeleteDialog(false); setPatientToDelete(null); loadPatients(); } catch (error) { console.error("Erro ao deletar paciente:", error); toast.error("Erro ao deletar paciente"); } finally { setLoading(false); } }; const handleCancelDelete = () => { setShowDeleteDialog(false); setPatientToDelete(null); }; const getPatientColor = ( index: number ): "blue" | "green" | "purple" | "orange" | "pink" | "teal" => { const colors: Array< "blue" | "green" | "purple" | "orange" | "pink" | "teal" > = ["blue", "green", "purple", "orange", "pink", "teal"]; return colors[index % colors.length]; }; return (
{/* Header */}

Pacientes

Gerencie os pacientes cadastrados

{/* Search and Filters */}
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400" />
Convênio:
{/* Table */}
{loading ? ( ) : filteredPatients.length === 0 ? ( ) : ( paginatedPatients.map((patient, index) => ( )) )}
Paciente Próximo Atendimento Convênio Ações
Carregando pacientes...
{searchTerm ? "Nenhum paciente encontrado com esse termo" : "Nenhum paciente encontrado"}

{patient.full_name}

{patient.email}

{patient.phone_mobile}

{/* TODO: Buscar próximo agendamento */}— {(patient as any).convenio || "Particular"}
{/* Paginação */} {filteredPatients.length > 0 && (
Mostrando {startIndex + 1} até {Math.min(endIndex, filteredPatients.length)} de {filteredPatients.length} pacientes
{(() => { const maxPagesToShow = 4; let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2)); let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1); if (endPage - startPage < maxPagesToShow - 1) { startPage = Math.max(1, endPage - maxPagesToShow + 1); } const pages = []; for (let i = startPage; i <= endPage; i++) { pages.push(i); } return pages.map((page) => ( )); })()}
)} {/* Modal de Formulário */} {showModal && (
{/* Header */}

{modalMode === "create" ? "Novo Paciente" : "Editar Paciente"}

{/* Form Content */}
)} {/* Modal de Visualizar Paciente */} {showViewModal && selectedPatient && (

Visualizar Paciente

Nome

{selectedPatient.full_name}

Email

{selectedPatient.email || '—'}

Telefone

{selectedPatient.phone_mobile || '—'}

Convênio

{(selectedPatient as any).convenio || 'Particular'}

)} {/* Delete Confirmation Dialog */} {showDeleteDialog && patientToDelete && (

Confirmar Exclusão

Tem certeza que deseja deletar o paciente{" "} {patientToDelete.full_name} ?

⚠️ Atenção: Esta ação é irreversível

  • • Todos os dados do paciente serão perdidos
  • • Histórico de consultas será mantido (por auditoria)
  • • Prontuários médicos serão mantidos (por legislação)
  • • O paciente precisará se cadastrar novamente
)}
); }