import { useState, useEffect } from "react"; import toast from "react-hot-toast"; import { Search, Plus, Eye, Edit, Trash2, X } from "lucide-react"; import { appointmentService, type Appointment, patientService, type Patient, doctorService, type Doctor, } from "../../services"; import { Avatar } from "../ui/Avatar"; import { CalendarPicker } from "../agenda/CalendarPicker"; import AvailableSlotsPicker from "../agenda/AvailableSlotsPicker"; interface AppointmentWithDetails extends Appointment { patient?: Patient; doctor?: Doctor; } export function SecretaryAppointmentList() { const [appointments, setAppointments] = useState( [] ); const [loading, setLoading] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("Todos"); const [typeFilter, setTypeFilter] = useState("Todos"); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage] = useState(10); const [showCreateModal, setShowCreateModal] = useState(false); const [modalMode, setModalMode] = useState<"create" | "edit">("create"); const [selectedAppointment, setSelectedAppointment] = useState(null); const [patients, setPatients] = useState([]); const [doctors, setDoctors] = useState([]); const [formData, setFormData] = useState({ id: undefined, patient_id: "", doctor_id: "", scheduled_at: "", appointment_type: "presencial", notes: "", }); const [selectedDate, setSelectedDate] = useState(""); const [selectedTime, setSelectedTime] = useState(""); const loadAppointments = async () => { setLoading(true); try { const data = await appointmentService.list(); // Buscar detalhes de pacientes e médicos const appointmentsWithDetails = await Promise.all( (Array.isArray(data) ? data : []).map(async (appointment) => { try { const [patient, doctor] = await Promise.all([ appointment.patient_id ? patientService.getById(appointment.patient_id) : null, appointment.doctor_id ? doctorService.getById(appointment.doctor_id) : null, ]); return { ...appointment, patient: patient || undefined, doctor: doctor || undefined, }; } catch (error) { console.error("Erro ao carregar detalhes:", error); return appointment; } }) ); setAppointments(appointmentsWithDetails); console.log("✅ Consultas carregadas:", appointmentsWithDetails); } catch (error) { console.error("❌ Erro ao carregar consultas:", error); toast.error("Erro ao carregar consultas"); setAppointments([]); } finally { setLoading(false); } }; useEffect(() => { loadAppointments(); loadDoctorsAndPatients(); }, []); // Se outro componente pediu para abrir o modal de criação com paciente pré-selecionado useEffect(() => { const openFromSession = () => { const patientId = sessionStorage.getItem("selectedPatientForAppointment"); if (patientId) { setFormData((prev: any) => ({ ...prev, patient_id: patientId })); setModalMode("create"); setShowCreateModal(true); sessionStorage.removeItem("selectedPatientForAppointment"); } }; // Try on mount openFromSession(); // Listen for explicit events const handler = () => openFromSession(); window.addEventListener("open-create-appointment", handler); return () => window.removeEventListener("open-create-appointment", handler); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Função de filtro const filteredAppointments = appointments.filter((appointment) => { // Filtro de busca por nome do paciente ou médico const searchLower = searchTerm.toLowerCase(); const matchesSearch = !searchTerm || appointment.patient?.full_name?.toLowerCase().includes(searchLower) || appointment.doctor?.full_name?.toLowerCase().includes(searchLower) || appointment.order_number?.toString().includes(searchTerm); // Mapeia o valor selecionado no select para o valor real usado na API/data const mapStatusFilterToValue = (label: string) => { if (label === "Todos") return null; // Se já é um valor de API, retorna ele mesmo if (['requested', 'confirmed', 'checked_in', 'in_progress', 'completed', 'cancelled', 'no_show'].includes(label)) { return label; } // Mantém mapeamento legado por compatibilidade const map: Record = { Confirmada: "confirmed", Agendada: "requested", Cancelada: "cancelled", Concluída: "completed", Concluida: "completed", }; return map[label] || label.toLowerCase(); }; const mapTypeFilterToValue = (label: string) => { if (label === "Todos") return null; const map: Record = { Presencial: "presencial", Telemedicina: "telemedicina", }; return map[label] || label.toLowerCase(); }; const statusValue = mapStatusFilterToValue(statusFilter); const typeValue = mapTypeFilterToValue(typeFilter); // Filtro de status const matchesStatus = statusValue === null || appointment.status === statusValue; // Filtro de tipo const matchesType = typeValue === null || appointment.appointment_type === typeValue; return matchesSearch && matchesStatus && matchesType; }); // Cálculos de paginação const totalPages = Math.ceil(filteredAppointments.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const paginatedAppointments = filteredAppointments.slice(startIndex, endIndex); const loadDoctorsAndPatients = async () => { try { const [patientsData, doctorsData] = await Promise.all([ patientService.list(), doctorService.list(), ]); setPatients(Array.isArray(patientsData) ? patientsData : []); setDoctors(Array.isArray(doctorsData) ? doctorsData : []); } catch (error) { console.error("Erro ao carregar pacientes e médicos:", error); } }; const handleOpenCreateModal = () => { setFormData({ patient_id: "", doctor_id: "", scheduled_at: "", appointment_type: "presencial", notes: "", }); setSelectedDate(""); setSelectedTime(""); setShowCreateModal(true); }; const handleCreateAppointment = async (e: React.FormEvent) => { e.preventDefault(); if (!formData.patient_id || !formData.doctor_id || !formData.scheduled_at) { toast.error("Preencha todos os campos obrigatórios"); return; } try { if (modalMode === "edit" && formData.id) { // Update only allowed fields per API types const updatePayload: any = {}; if (formData.scheduled_at) updatePayload.scheduled_at = new Date( formData.scheduled_at ).toISOString(); if (formData.notes) updatePayload.notes = formData.notes; await appointmentService.update(formData.id, updatePayload); toast.success("Consulta atualizada com sucesso!"); } else { await appointmentService.create({ patient_id: formData.patient_id, doctor_id: formData.doctor_id, scheduled_at: new Date(formData.scheduled_at).toISOString(), appointment_type: formData.appointment_type as | "presencial" | "telemedicina", patient_notes: formData.notes, }); toast.success("Consulta agendada com sucesso!"); } setShowCreateModal(false); loadAppointments(); } catch (error) { console.error("Erro ao criar consulta:", error); toast.error("Erro ao agendar consulta"); } }; const handleSearch = () => { loadAppointments(); }; const handleViewAppointment = (appointment: AppointmentWithDetails) => { setSelectedAppointment(appointment); }; const handleEditAppointment = (appointment: AppointmentWithDetails) => { setModalMode("edit"); setFormData({ id: appointment.id, patient_id: appointment.patient_id || "", doctor_id: appointment.doctor_id || "", scheduled_at: appointment.scheduled_at || "", appointment_type: appointment.appointment_type || "presencial", notes: appointment.notes || "", }); setShowCreateModal(true); }; const handleClear = () => { setSearchTerm(""); setStatusFilter("Todos"); setTypeFilter("Todos"); setCurrentPage(1); loadAppointments(); }; const handleStatusChange = async (appointmentId: string, newStatus: string) => { try { console.log(`[SecretaryAppointmentList] Atualizando status da consulta ${appointmentId} para ${newStatus}`); await appointmentService.update(appointmentId, { status: newStatus as any }); toast.success(`Status atualizado para: ${ newStatus === 'requested' ? 'Solicitada' : newStatus === 'confirmed' ? 'Confirmada' : newStatus === 'checked_in' ? 'Check-in' : newStatus === 'in_progress' ? 'Em Atendimento' : newStatus === 'completed' ? 'Concluída' : newStatus === 'cancelled' ? 'Cancelada' : newStatus === 'no_show' ? 'Não Compareceu' : newStatus }`); loadAppointments(); } catch (error) { console.error("Erro ao atualizar status:", error); toast.error("Erro ao atualizar status da consulta"); } }; const handleDeleteAppointment = async (appointmentId: string) => { if (!confirm("Tem certeza que deseja cancelar esta consulta?")) { return; } try { await appointmentService.update(appointmentId, { status: "cancelled", cancelled_at: new Date().toISOString(), cancellation_reason: "Cancelado pela secretaria" }); toast.success("Consulta cancelada com sucesso!"); loadAppointments(); } catch (error) { console.error("Erro ao cancelar consulta:", error); toast.error("Erro ao cancelar consulta"); } }; // Reset página quando filtros mudarem useEffect(() => { setCurrentPage(1); }, [searchTerm, statusFilter, typeFilter]); const getStatusBadge = (status: string) => { const statusMap: Record = { confirmada: { label: "Confirmada", className: "bg-green-100 text-green-700", }, agendada: { label: "Agendada", className: "bg-blue-100 text-blue-700" }, cancelada: { label: "Cancelada", className: "bg-red-100 text-red-700" }, concluida: { label: "Concluída", className: "bg-gray-100 text-gray-700" }, }; const config = statusMap[status] || { label: status, className: "bg-gray-100 text-gray-700", }; return ( {config.label} ); }; const formatDate = (dateString: string) => { try { const date = new Date(dateString); return date.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", }); } catch { return "—"; } }; const formatTime = (dateString: string) => { try { const date = new Date(dateString); return date.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit", }); } catch { return "—"; } }; return (
{/* Header */}

Consultas

Gerencie as consultas agendadas

{/* 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" />
Status:
Tipo:
{/* Table */}
{loading ? ( ) : filteredAppointments.length === 0 ? ( ) : ( paginatedAppointments.map((appointment) => ( )) )}
Paciente Médico Data/Hora Tipo Status Ações
Carregando consultas...
{searchTerm || statusFilter !== "Todos" || typeFilter !== "Todos" ? "Nenhuma consulta encontrada com esses filtros" : "Nenhuma consulta encontrada"}

{appointment.patient?.full_name || "Paciente não encontrado"}

{appointment.patient?.email || "—"}

{appointment.doctor?.full_name || "Médico não encontrado"}

{appointment.doctor?.specialty || "—"}

{appointment.scheduled_at ? ( <>
{formatDate(appointment.scheduled_at)}
{formatTime(appointment.scheduled_at)}
) : ( "—" )}
{appointment.appointment_type === "telemedicina" ? "Telemedicina" : "Presencial"}
{/* Paginação */} {filteredAppointments.length > 0 && (
Mostrando {startIndex + 1} até {Math.min(endIndex, filteredAppointments.length)} de {filteredAppointments.length} consultas
{(() => { const maxPagesToShow = 4; let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2)); let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1); // Ajusta startPage se estivermos próximos do fim 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 Criar Consulta */} {showCreateModal && (

{modalMode === "edit" ? "Editar Consulta" : "Nova Consulta"}

{/* Calendário Visual */}
{formData.doctor_id ? ( { setSelectedDate(date); setSelectedTime(""); // Resetar horário ao mudar data setFormData({ ...formData, scheduled_at: "" }); }} /> ) : (
Selecione um médico primeiro para ver a disponibilidade
)}
{/* Seletor de Horários */} {selectedDate && formData.doctor_id && (
{ setSelectedTime(time); // Combinar data + horário no formato ISO const datetime = `${selectedDate}T${time}:00`; setFormData({ ...formData, scheduled_at: datetime }); }} />
)}