import React, { useState, useMemo, useEffect, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import API_KEY from "../components/utils/apiKeys.js"; import AgendamentoCadastroManager from "../pages/AgendamentoCadastroManager.jsx"; import { GetAllDoctors } from "../components/utils/Functions-Endpoints/Doctor.js"; import { useAuth } from "../components/utils/AuthProvider.js"; import dayjs from "dayjs"; import "dayjs/locale/pt-br"; import isBetween from "dayjs/plugin/isBetween"; import localeData from "dayjs/plugin/localeData"; import { Search, ChevronLeft, ChevronRight, Edit, Trash2, CheckCircle, } from "lucide-react"; import "../pages/style/Agendamento.css"; import "../pages/style/FilaEspera.css"; import Spinner from "../components/Spinner.jsx"; dayjs.locale("pt-br"); dayjs.extend(isBetween); dayjs.extend(localeData); const Agendamento = () => { const navigate = useNavigate(); const { getAuthorizationHeader, user } = useAuth(); const authHeader = getAuthorizationHeader(); const ID_MEDICO_ESPECIFICO = "078d2a67-b4c1-43c8-ae32-c1e75bb5b3df"; const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]); const [selectedID, setSelectedId] = useState("0"); const [filaEsperaData, setFilaEsperaData] = useState([]); const [FiladeEspera, setFiladeEspera] = useState(false); const [PageNovaConsulta, setPageConsulta] = useState(false); const [DictAgendamentosOrganizados, setAgendamentosOrganizados] = useState( {} ); const [showDeleteModal, setShowDeleteModal] = useState(false); const [ListaDeMedicos, setListaDeMedicos] = useState([]); const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]); const [searchTermDoctor, setSearchTermDoctor] = useState(""); const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" }); const [motivoCancelamento, setMotivoCancelamento] = useState(""); const [searchTermPatient, setSearchTermPatient] = useState(""); const [patientDropdownOpen, setPatientDropdownOpen] = useState(false); const [showSpinner, setShowSpinner] = useState(true); const [waitlistSearch, setWaitlistSearch] = useState(""); const [waitSortKey, setWaitSortKey] = useState(null); const [waitSortDir, setWaitSortDir] = useState("asc"); const [waitPage, setWaitPage] = useState(1); const [waitPerPage, setWaitPerPage] = useState(10); const [cacheMedicos, setCacheMedicos] = useState({}); const [cachePacientes, setCachePacientes] = useState({}); const [currentDate, setCurrentDate] = useState(dayjs()); const [selectedDay, setSelectedDay] = useState(dayjs()); const [agendamentoParaEdicao, setAgendamentoParaEdicao] = useState(null); const [quickJump, setQuickJump] = useState({ month: currentDate.month(), year: currentDate.year(), }); const fetchAppointments = useCallback(async () => { if (!authHeader) return; setShowSpinner(true); const myHeaders = new Headers(); myHeaders.append("Authorization", authHeader); myHeaders.append("apikey", API_KEY); const requestOptions = { method: "GET", headers: myHeaders, redirect: "follow", }; const apiUrl = `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?doctor_id=eq.${ID_MEDICO_ESPECIFICO}&select=*`; try { const res = await fetch(apiUrl, requestOptions); const data = await res.json(); setListaTodosAgendamentos(data || []); } catch (err) { console.error("Erro ao buscar agendamentos", err); setListaTodosAgendamentos([]); } finally { setShowSpinner(false); } }, [authHeader, ID_MEDICO_ESPECIFICO]); const updateAppointmentStatus = useCallback( async (id, updates) => { setShowSpinner(true); const myHeaders = new Headers(); myHeaders.append("Authorization", authHeader); myHeaders.append("apikey", API_KEY); myHeaders.append("Content-Type", "application/json"); myHeaders.append("Prefer", "return=representation"); const requestOptions = { method: "PATCH", headers: myHeaders, body: JSON.stringify(updates), }; try { const response = await fetch( `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${id}`, requestOptions ); if (response.ok) { await fetchAppointments(); return true; } else { console.error( "Erro ao atualizar agendamento:", await response.text() ); return false; } } catch (error) { console.error("Erro de rede/servidor:", error); return false; } finally { setShowSpinner(false); } }, [authHeader, fetchAppointments] ); const deleteConsulta = useCallback( async (id) => { const success = await updateAppointmentStatus(id, { status: "cancelled", cancellation_reason: motivoCancelamento, updated_at: new Date().toISOString(), }); if (success) { setShowDeleteModal(false); setMotivoCancelamento(""); setSelectedId("0"); } else { alert("Falha ao cancelar a consulta."); } }, [motivoCancelamento, updateAppointmentStatus] ); const confirmConsulta = useCallback( async (id) => { const success = await updateAppointmentStatus(id, { status: "agendado", cancellation_reason: null, updated_at: new Date().toISOString(), }); if (success) { setSelectedId("0"); } else { alert("Falha ao reverter o cancelamento."); } }, [updateAppointmentStatus] ); useEffect(() => { if (authHeader) { fetchAppointments(); if (user?.role !== "doctor") { GetAllDoctors(authHeader).then((docs) => { if (docs) { setListaDeMedicos( docs.map((d) => ({ nomeMedico: d.full_name, idMedico: d.id })) ); } }); } } }, [authHeader, fetchAppointments, user?.role]); useEffect(() => { const processData = async () => { if (!listaTodosAgendamentos.length) { setAgendamentosOrganizados({}); setFilaEsperaData([]); return; } setShowSpinner(true); const appointmentsToShow = listaTodosAgendamentos; const patientIdsToFetch = new Set(); const doctorIdsToFetch = new Set(); appointmentsToShow.forEach((ag) => { if (ag.patient_id && !cachePacientes[ag.patient_id]) { patientIdsToFetch.add(ag.patient_id); } if (ag.doctor_id && !cacheMedicos[ag.doctor_id]) { doctorIdsToFetch.add(ag.doctor_id); } }); const fetchPromises = []; if (patientIdsToFetch.size > 0) { const query = `id=in.(${Array.from(patientIdsToFetch).join(",")})`; fetchPromises.push( fetch( `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients?${query}&select=*`, { headers: { apikey: API_KEY, Authorization: authHeader }, } ).then((res) => res.json()) ); } else { fetchPromises.push(Promise.resolve(null)); } if (doctorIdsToFetch.size > 0) { const query = `id=in.(${Array.from(doctorIdsToFetch).join(",")})`; fetchPromises.push( fetch( `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?${query}&select=id,full_name`, { headers: { apikey: API_KEY, Authorization: authHeader }, } ).then((res) => res.json()) ); } else { fetchPromises.push(Promise.resolve(null)); } const [newPatients, newDoctors] = await Promise.all(fetchPromises); const updatedPatientCache = { ...cachePacientes }; if (newPatients) newPatients.forEach((p) => (updatedPatientCache[p.id] = p)); const updatedDoctorCache = { ...cacheMedicos }; if (newDoctors) newDoctors.forEach((d) => (updatedDoctorCache[d.id] = d)); setCachePacientes(updatedPatientCache); setCacheMedicos(updatedDoctorCache); const newDict = {}; const newFila = []; for (const agendamento of appointmentsToShow) { const medico = updatedDoctorCache[agendamento.doctor_id]; const paciente = updatedPatientCache[agendamento.patient_id]; if (!medico || !paciente) continue; const agendamentoMelhorado = { ...agendamento, medico_nome: medico.full_name || "N/A", paciente_nome: paciente.full_name || "N/A", paciente_cpf: paciente.cpf || "N/A", }; if (agendamento.status === "requested") { newFila.push({ agendamento: agendamentoMelhorado, Infos: agendamentoMelhorado, }); } else { const DiaAgendamento = dayjs(agendamento.scheduled_at).format( "YYYY-MM-DD" ); if (!newDict[DiaAgendamento]) newDict[DiaAgendamento] = []; newDict[DiaAgendamento].push(agendamentoMelhorado); } } for (const key in newDict) { newDict[key].sort( (a, b) => new Date(a.scheduled_at) - new Date(b.scheduled_at) ); } setAgendamentosOrganizados(newDict); setFilaEsperaData(newFila); setShowSpinner(false); }; processData(); }, [listaTodosAgendamentos, authHeader]); const handleEditConsulta = (agendamento) => { setAgendamentoParaEdicao(agendamento); setPageConsulta(true); }; const handleSearchMedicos = (term) => { setSearchTermDoctor(term); if (term.trim()) { const filtered = ListaDeMedicos.filter((medico) => medico.nomeMedico.toLowerCase().includes(term.toLowerCase()) ); setFiltredTodosMedicos(filtered); } else { setFiltredTodosMedicos([]); setMedicoFiltrado({ id: "vazio" }); } }; const filtrarPorPaciente = (appointments) => { if (!searchTermPatient.trim()) return appointments; const term = searchTermPatient.toLowerCase(); return appointments.filter((app) => app.paciente_nome?.toLowerCase().includes(term) ); }; const generateDateGrid = () => { const grid = []; const startOfMonth = currentDate.startOf("month"); let currentDay = startOfMonth.subtract(startOfMonth.day(), "day"); for (let i = 0; i < 42; i++) { grid.push(currentDay); currentDay = currentDay.add(1, "day"); } return grid; }; const dateGrid = useMemo(() => generateDateGrid(), [currentDate]); const weekDays = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"]; const handleDateClick = (day) => setSelectedDay(day); const DeleteModal = () => (
Confirmação de Cancelamento

Qual o motivo do cancelamento?

); useEffect(() => { setQuickJump({ month: currentDate.month(), year: currentDate.year(), }); }, [currentDate]); const filaEsperaFiltrada = useMemo(() => { if (!waitlistSearch.trim()) return filaEsperaData; const term = waitlistSearch.toLowerCase(); return filaEsperaData.filter( (item) => (item?.Infos?.paciente_nome?.toLowerCase() || "").includes(term) || (item?.Infos?.paciente_cpf?.toLowerCase() || "").includes(term) || (item?.Infos?.medico_nome?.toLowerCase() || "").includes(term) ); }, [waitlistSearch, filaEsperaData]); const applySortingWaitlist = useCallback( (arr) => { if (!Array.isArray(arr) || !waitSortKey) return arr; const copy = [...arr]; const key = waitSortKey; const dir = waitSortDir === "asc" ? 1 : -1; copy.sort((a, b) => { const valA = key === "data" ? new Date(a.agendamento.scheduled_at) : a.Infos?.[`${key}_nome`] || ""; const valB = key === "data" ? new Date(b.agendamento.scheduled_at) : b.Infos?.[`${key}_nome`] || ""; if (valA < valB) return -1 * dir; if (valA > valB) return 1 * dir; return 0; }); return copy; }, [waitSortKey, waitSortDir] ); const filaEsperaOrdenada = useMemo( () => applySortingWaitlist(filaEsperaFiltrada), [filaEsperaFiltrada, applySortingWaitlist] ); const listaPacientesUnicos = useMemo(() => { const pacientes = Object.values(cachePacientes || {}); return pacientes.sort((a, b) => (a.full_name || "").localeCompare(b.full_name || "") ); }, [cachePacientes]); const waitTotalPages = Math.ceil(filaEsperaOrdenada.length / waitPerPage) || 1; const waitIndiceInicial = (waitPage - 1) * waitPerPage; const waitIndiceFinal = waitIndiceInicial + waitPerPage; const filaEsperaPaginada = filaEsperaOrdenada.slice( waitIndiceInicial, waitIndiceFinal ); const gerarNumerosWaitPages = () => { const paginas = []; const paginasParaMostrar = 5; let inicio = Math.max(1, waitPage - Math.floor(paginasParaMostrar / 2)); let fim = Math.min(waitTotalPages, inicio + paginasParaMostrar - 1); inicio = Math.max(1, fim - paginasParaMostrar + 1); for (let i = inicio; i <= fim; i++) paginas.push(i); return paginas; }; useEffect(() => { setWaitPage(1); }, [waitlistSearch, waitSortKey, waitSortDir]); const handleQuickJumpChange = (type, value) => { setQuickJump((prev) => ({ ...prev, [type]: Number(value) })); }; const applyQuickJump = () => { let newDate = dayjs().year(quickJump.year).month(quickJump.month).date(1); setCurrentDate(newDate); setSelectedDay(newDate); }; return (

Agendar nova consulta

{!PageNovaConsulta ? (
{!FiladeEspera ? (
{selectedDay.format("MMM")} {selectedDay.format("DD")}

{selectedDay.format("dddd")}

{selectedDay.format("D [de] MMMM [de] YYYY")}

Realizado
Confirmado
Agendado
Cancelado

Consultas para {selectedDay.format("DD/MM")}

{showSpinner ? ( ) : (() => { const appointmentsForDay = DictAgendamentosOrganizados[selectedDay.format("YYYY-MM-DD")] || []; // 1º filtro: por médico (se tiver) const filteredByDoctor = appointmentsForDay.filter( (app) => MedicoFiltrado.id === "vazio" || app.doctor_id === MedicoFiltrado.id ); // 2º filtro: por paciente (campo de cima) const filteredByPatient = filtrarPorPaciente(filteredByDoctor); if (filteredByPatient.length === 0) { return (

{searchTermPatient ? `Nenhuma consulta encontrada para "${searchTermPatient}".` : "Nenhuma consulta agendada."}

); } return filteredByPatient.map((app) => (
{dayjs(app.scheduled_at).format("HH:mm")}
{app.paciente_nome} Dr(a). {app.medico_nome}
{app.status === "cancelled" ? ( ) : ( )} {app.status !== "cancelled" && ( )}
)); })()}
Filtrar por Paciente
{ setSearchTermPatient(e.target.value); setPatientDropdownOpen(true); }} onFocus={() => { if (searchTermPatient.trim()) { setPatientDropdownOpen(true); } }} style={{ paddingLeft: "40px" }} /> {patientDropdownOpen && searchTermPatient.trim() && (
{listaPacientesUnicos .filter((p) => { const nome = (p.full_name || "").toLowerCase(); const term = searchTermPatient.toLowerCase(); return nome.startsWith(term); // só começo do nome }) .slice(0, 20) .map((p) => ( ))}
)}
{searchTermPatient && (
)}

{currentDate.format("MMMM [de] YYYY")}

{weekDays.map((day) => (
{day}
))} {dateGrid.map((day, index) => { const dayString = day.format("YYYY-MM-DD"); const appointmentsOnDay = DictAgendamentosOrganizados[dayString] || []; const filteredAppointments = filtrarPorPaciente( appointmentsOnDay.filter( (app) => MedicoFiltrado.id === "vazio" || app.doctor_id === MedicoFiltrado.id ) ); const cellClasses = `day-cell ${ day.isSame(currentDate, "month") ? "current-month" : "other-month" } ${day.isSame(dayjs(), "day") ? "today" : ""} ${ day.isSame(selectedDay, "day") ? "selected" : "" }`; return (
handleDateClick(day)} > {day.format("D")} {filteredAppointments.length > 0 && (
{filteredAppointments.length}
)}
); })}
) : (

Fila de Espera

{" "} Filtros
setWaitlistSearch(e.target.value) } /> Digite o nome do paciente, CPF ou nome do médico
Ordenar por:
{filaEsperaFiltrada.length} DE{" "} {filaEsperaData.length} SOLICITAÇÕES ENCONTRADAS
{filaEsperaPaginada.length > 0 ? ( filaEsperaPaginada.map((item, index) => ( )) ) : ( )}
Nome do Paciente CPF Médico Solicitado Data da Solicitação Ações
{item?.Infos?.paciente_nome} {item?.Infos?.paciente_cpf} {item?.Infos?.medico_nome} {dayjs( item.agendamento.scheduled_at ).format("DD/MM/YYYY")}
{showSpinner ? ( ) : ( <>

Nenhuma solicitação encontrada.

)}
{filaEsperaFiltrada.length > 0 && (
Itens por página:
Página {waitPage} de {waitTotalPages} • Mostrando {waitIndiceInicial + 1}- {Math.min( waitIndiceFinal, filaEsperaFiltrada.length )}{" "} de {filaEsperaFiltrada.length}
)}
)}
) : ( { setPageConsulta(false); fetchAppointments(); }} /> )} {showDeleteModal && }
); }; export default Agendamento;