diff --git a/src/PagesMedico/DoctorAgendamentoManager.jsx b/src/PagesMedico/DoctorAgendamentoManager.jsx index 7f0d77e..34eb294 100644 --- a/src/PagesMedico/DoctorAgendamentoManager.jsx +++ b/src/PagesMedico/DoctorAgendamentoManager.jsx @@ -1,654 +1,1018 @@ -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 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"; // Removidos imports não utilizados no novo fluxo -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 { 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'; +import "../pages/style/FilaEspera.css"; +import Spinner from "../components/Spinner.jsx"; - -dayjs.locale('pt-br'); +dayjs.locale("pt-br"); dayjs.extend(isBetween); dayjs.extend(localeData); - const Agendamento = () => { - const navigate = useNavigate(); - const { getAuthorizationHeader, user } = useAuth(); - const authHeader = getAuthorizationHeader(); + const navigate = useNavigate(); + const { getAuthorizationHeader, user } = useAuth(); + const authHeader = getAuthorizationHeader(); - // ID do médico que você quer visualizar - const ID_MEDICO_ESPECIFICO = "078d2a67-b4c1-43c8-ae32-c1e75bb5b3df"; + // ID do médico que você quer visualizar + 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 [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 [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 [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(), + }); - // ✨ ALTERAÇÃO PRINCIPAL: A busca agora filtra pelo ID do médico direto na API - 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' }; + // ✨ ALTERAÇÃO PRINCIPAL: A busca agora filtra pelo ID do médico direto na API + 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", + }; - // A URL agora contém o filtro para o ID do médico específico - const apiUrl = `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?doctor_id=eq.${ID_MEDICO_ESPECIFICO}&select=*`; + // A URL agora contém o filtro para o ID do médico específico + 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]); + 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'); + 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 { - alert("Falha ao cancelar a consulta."); + console.error( + "Erro ao atualizar agendamento:", + await response.text() + ); + return false; } - }, [motivoCancelamento, updateAppointmentStatus]); + } 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."); + 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(); + // A busca de todos os médicos pode continuar caso a secretária precise ver a lista + 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 () => { + // Como os dados já vêm filtrados da API, não precisamos mais da verificação de 'user' aqui + if (!listaTodosAgendamentos.length) { + setAgendamentosOrganizados({}); + setFilaEsperaData([]); + return; + } + + setShowSpinner(true); + + // ✨ SIMPLIFICAÇÃO: Não é mais necessário filtrar por `user.role`, + // pois a API já retornou apenas os agendamentos do médico desejado. + 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); } - }, [updateAppointmentStatus]); - - useEffect(() => { - if(authHeader) { - fetchAppointments(); - // A busca de todos os médicos pode continuar caso a secretária precise ver a lista - if (user?.role !== 'doctor') { - GetAllDoctors(authHeader).then(docs => { - if (docs) { - setListaDeMedicos(docs.map(d => ({ nomeMedico: d.full_name, idMedico: d.id }))); - } - }); - } + if (ag.doctor_id && !cacheMedicos[ag.doctor_id]) { + doctorIdsToFetch.add(ag.doctor_id); } - }, [authHeader, fetchAppointments, user?.role]); + }); - useEffect(() => { - const processData = async () => { - // Como os dados já vêm filtrados da API, não precisamos mais da verificação de 'user' aqui - if (!listaTodosAgendamentos.length) { - setAgendamentosOrganizados({}); - setFilaEsperaData([]); - return; + 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)); // Mantém a ordem do Promise.all + } - setShowSpinner(true); - - // ✨ SIMPLIFICAÇÃO: Não é mais necessário filtrar por `user.role`, - // pois a API já retornou apenas os agendamentos do médico desejado. - 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)); // Mantém a ordem do Promise.all + 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)); + } - 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 [newPatients, newDoctors] = await Promise.all(fetchPromises); - const updatedPatientCache = { ...cachePacientes }; - if (newPatients) newPatients.forEach(p => updatedPatientCache[p.id] = p); + 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 updatedDoctorCache = { ...cacheMedicos }; + if (newDoctors) newDoctors.forEach((d) => (updatedDoctorCache[d.id] = d)); - const newDict = {}; - const newFila = []; + setCachePacientes(updatedPatientCache); + setCacheMedicos(updatedDoctorCache); - for (const agendamento of appointmentsToShow) { - const medico = updatedDoctorCache[agendamento.doctor_id]; - const paciente = updatedPatientCache[agendamento.patient_id]; + const newDict = {}; + const newFila = []; - if (!medico || !paciente) continue; + for (const agendamento of appointmentsToShow) { + const medico = updatedDoctorCache[agendamento.doctor_id]; + const paciente = updatedPatientCache[agendamento.patient_id]; - const agendamentoMelhorado = { - ...agendamento, - medico_nome: medico.full_name || 'N/A', - paciente_nome: paciente.full_name || 'N/A', - paciente_cpf: paciente.cpf || 'N/A' - }; + if (!medico || !paciente) continue; - 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); + const agendamentoMelhorado = { + ...agendamento, + medico_nome: medico.full_name || "N/A", + paciente_nome: paciente.full_name || "N/A", + paciente_cpf: paciente.cpf || "N/A", }; - processData(); - }, [listaTodosAgendamentos, authHeader]); // Removido 'user' das dependências pois não é mais usado aqui - - // O restante do código permanece o mesmo... - - 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); + if (agendamento.status === "requested") { + newFila.push({ + agendamento: agendamentoMelhorado, + Infos: agendamentoMelhorado, + }); } else { - setFiltredTodosMedicos([]); - setMedicoFiltrado({ id: "vazio" }); + const DiaAgendamento = dayjs(agendamento.scheduled_at).format( + "YYYY-MM-DD" + ); + if (!newDict[DiaAgendamento]) newDict[DiaAgendamento] = []; + newDict[DiaAgendamento].push(agendamentoMelhorado); } - }; - - 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? (Opcional)

- -
-
- - {/* ✨ Botão sempre ativo ✨ */} - -
-
-
-
-); - - - 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) + for (const key in newDict) { + newDict[key].sort( + (a, b) => new Date(a.scheduled_at) - new Date(b.scheduled_at) ); - }, [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 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; + setAgendamentosOrganizados(newDict); + setFilaEsperaData(newFila); + setShowSpinner(false); }; - useEffect(() => { setWaitPage(1); }, [waitlistSearch, waitSortKey, waitSortDir]); + processData(); + }, [listaTodosAgendamentos, authHeader]); // Removido 'user' das dependências pois não é mais usado aqui - const handleQuickJumpChange = (type, value) => { - setQuickJump(prev => ({ ...prev, [type]: Number(value) })); - }; + // O restante do código permanece o mesmo... - const applyQuickJump = () => { - let newDate = dayjs().year(quickJump.year).month(quickJump.month).date(1); - setCurrentDate(newDate); - setSelectedDay(newDate); - }; + const handleEditConsulta = (agendamento) => { + setAgendamentoParaEdicao(agendamento); + setPageConsulta(true); + }; - return ( -
-

Agendar nova consulta

-
- - - -
- {!PageNovaConsulta ? ( -
- {user?.role !== 'doctor' && ( -
-
-
-
-
- handleSearchMedicos(e.target.value)} /> -
-
- {searchTermDoctor && FiltredTodosMedicos.length > 0 && ( -
- {FiltredTodosMedicos.map((medico) => ( -
{ - setSearchTermDoctor(medico.nomeMedico); - setFiltredTodosMedicos([]); - setMedicoFiltrado({ id: medico.idMedico }); - }} - > -

{medico.nomeMedico}

-
- ))} -
- )} -
-
-
- )} -
- - -
-
- {!FiladeEspera ? ( -
-
-
{selectedDay.format('MMM')}{selectedDay.format('DD')}
-

{selectedDay.format('dddd')}

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

-
-

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

- {showSpinner ? : (DictAgendamentosOrganizados[selectedDay.format('YYYY-MM-DD')]?.filter(app => MedicoFiltrado.id === "vazio" || app.doctor_id === MedicoFiltrado.id).length > 0) ? ( - DictAgendamentosOrganizados[selectedDay.format('YYYY-MM-DD')] - .filter(app => MedicoFiltrado.id === "vazio" || app.doctor_id === MedicoFiltrado.id) - .map(app => ( -
-
{dayjs(app.scheduled_at).format('HH:mm')}
-
{app.paciente_nome}Dr(a). {app.medico_nome}
-
- {app.status === 'cancelled' ? ( - - ) : ( - - )} - {app.status !== 'cancelled' && ( - - )} -
-
- )) - ) : (

Nenhuma consulta agendada.

)} -
-
-
-
-
Realizado
-
Confirmado
-
Agendado
-
Cancelado
-
-
-
-

{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 = 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 PacienteCPFMédico SolicitadoData da SolicitaçãoAçõ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 && } + 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 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? (Opcional)

+ +
+
+ + {/* ✨ Botão sempre ativo ✨ */} + +
+
+
+ ); + + 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 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 ? ( +
+ {user?.role !== "doctor" && ( +
+
+
+
+
+ handleSearchMedicos(e.target.value)} + /> +
+
+ {searchTermDoctor && FiltredTodosMedicos.length > 0 && ( +
+ {FiltredTodosMedicos.map((medico) => ( +
{ + setSearchTermDoctor(medico.nomeMedico); + setFiltredTodosMedicos([]); + setMedicoFiltrado({ id: medico.idMedico }); + }} + > +

{medico.nomeMedico}

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

{selectedDay.format("dddd")}

+

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

+
+
+

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

+ {showSpinner ? ( + + ) : DictAgendamentosOrganizados[ + selectedDay.format("YYYY-MM-DD") + ]?.filter( + (app) => + MedicoFiltrado.id === "vazio" || + app.doctor_id === MedicoFiltrado.id + ).length > 0 ? ( + DictAgendamentosOrganizados[ + selectedDay.format("YYYY-MM-DD") + ] + .filter( + (app) => + MedicoFiltrado.id === "vazio" || + app.doctor_id === MedicoFiltrado.id + ) + .map((app) => ( +
+
+ {dayjs(app.scheduled_at).format("HH:mm")} +
+
+ {app.paciente_nome} + Dr(a). {app.medico_nome} +
+
+ {app.status === "cancelled" ? ( + + ) : ( + + )} + {app.status !== "cancelled" && ( + + )} +
+
+ )) + ) : ( +
+

Nenhuma consulta agendada.

+
+ )} +
+
+
+
+
+ Realizado +
+
+ Confirmado +
+
+ Agendado +
+
+ Cancelado +
+
+
+
+

{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 = 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 PacienteCPFMédico SolicitadoData da SolicitaçãoAçõ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; diff --git a/src/pages/FinanceiroDashboard.jsx b/src/pages/FinanceiroDashboard.jsx index b3768ce..d2bf772 100644 --- a/src/pages/FinanceiroDashboard.jsx +++ b/src/pages/FinanceiroDashboard.jsx @@ -86,7 +86,7 @@ function mockFetchPagamentos() { { id: "PAY-001", paciente: { nome: "Sarah Oliveira", convenio: "Unimed" }, - valor: 20000, + valor: 20000, forma_pagamento: "Cartão", data_vencimento: "2025-09-30", status: "pendente", @@ -96,7 +96,7 @@ function mockFetchPagamentos() { { id: "PAY-002", paciente: { nome: "Laissa Marquetti", convenio: "Bradesco Saúde" }, - valor: 15000, + valor: 15000, forma_pagamento: "Dinheiro", data_vencimento: "2025-09-15", status: "pago", @@ -106,14 +106,14 @@ function mockFetchPagamentos() { { id: "PAY-003", paciente: { nome: "Vera Santos", convenio: "Particular" }, - valor: 30000, + valor: 30000, forma_pagamento: "Pix", data_vencimento: "2025-09-20", - status: "vencido", + status: "vencido", desconto: 0, observacoes: "Não respondeu ao contato" }, - { + { id: "PAY-004", paciente: { nome: "Carlos Almeida", convenio: "Particular" }, valor: 10000, @@ -169,7 +169,7 @@ export default function FinanceiroDashboard() { recebido += valorLiquido; descontos += p.desconto; } else { - aReceber += p.valor; + aReceber += valorLiquido; } }); @@ -244,8 +244,7 @@ export default function FinanceiroDashboard() {
@@ -416,15 +415,14 @@ export default function FinanceiroDashboard() {
-
@@ -432,4 +430,4 @@ export default function FinanceiroDashboard() { )} ); -} +} \ No newline at end of file diff --git a/src/pages/style/Agendamento.css b/src/pages/style/Agendamento.css index 6a4964f..806e78c 100644 --- a/src/pages/style/Agendamento.css +++ b/src/pages/style/Agendamento.css @@ -273,6 +273,7 @@ justify-content: space-between; align-items: center; margin: 15px 0 20px; + color: #fff; } .tabs-agenda-fila { diff --git a/src/pages/style/FinanceiroDashboard.css b/src/pages/style/FinanceiroDashboard.css index d9caac7..2d9fb6a 100644 --- a/src/pages/style/FinanceiroDashboard.css +++ b/src/pages/style/FinanceiroDashboard.css @@ -39,6 +39,7 @@ margin: 0 0 8px 0; font-size: 14px; font-weight: 500; + color: #fff; opacity: 0.9; } @@ -107,35 +108,79 @@ } /* Botões de ação */ -.action-group { - display: flex; - gap: 8px; - align-items: center; + +.btn-view { + background-color: #E6F2FF !important; + color: #004085 !important; + border: 1px solid #B8D4F0 !important; + padding: 0.375rem 0.75rem; + font-size: 0.875rem; + border-radius: 6px; + cursor: pointer; + transition: all 0.15s ease-in-out; + text-decoration: none; + display: inline-block; + text-align: center; } -.action-btn { - cursor: pointer; - padding: 6px 12px; - border-radius: 6px; - border: 1px solid #d7e6fb; - background: #fff; - transition: all 0.2s ease; - font-size: 13px; +.btn-view:hover { + background-color: #D1E7FF !important; + border-color: #9EC5FE !important; } -.action-btn:hover { - background: #f6f9fc; - border-color: #93c5fd; +.btn-edit { + background-color: #FFF3CD !important; + color: #856404 !important; + border: 1px solid #FFEAA7 !important; + padding: 0.375rem 0.75rem; + font-size: 0.875rem; + border-radius: 6px; + cursor: pointer; + transition: all 0.15s ease-in-out; + text-decoration: none; + display: inline-block; + text-align: center; } -.action-btn.delete { - border-color: #fca5a5; - color: #b91c1c; +.btn-edit:hover { + background-color: #FFEEBA !important; + border-color: #FFE087 !important; } -.action-btn.delete:hover { - background: #fee2e2; - border-color: #ef4444; +.btn-delete:hover { + background-color: #F1B0B7 !important; + border-color: #ED969E !important; +} +.btn-delete { + background-color: #F8D7DA !important; + color: #721C24 !important; + border: 1px solid #F5C6CB !important; + padding: 0.375rem 0.75rem; + font-size: 0.875rem; + border-radius: 6px; + cursor: pointer; + transition: all 0.15s ease-in-out; + text-decoration: none; + display: inline-block; + text-align: center; +} + +html[data-bs-theme="dark"] .btn-view { + background-color: #1e3a8a !important; + color: #e0e0e0 !important; + border-color: #374151 !important; +} + +html[data-bs-theme="dark"] .btn-edit { + background-color: #78350f !important; + color: #fef3c7 !important; + border-color: #374151 !important; +} + +html[data-bs-theme="dark"] .btn-delete { + background-color: #7f1d1d !important; + color: #fee2e2 !important; + border-color: #374151 !important; } /* Badges de status */ @@ -182,8 +227,8 @@ padding: 24px; width: 100%; max-width: 550px; - max-height: 90vh; - overflow-y: auto; + max-height: 85vh; + overflow-y: auto; box-sizing: border-box; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); } @@ -208,6 +253,12 @@ gap: 16px; } +.modal-card .input-field, +.modal-card .select-field, +.modal-card textarea { + width: 100%; +} + .form-group { display: flex; flex-direction: column; @@ -241,12 +292,23 @@ gap: 10px; margin-top: 24px; } +.input-field, +.select-field, +textarea { + padding: 10px 12px; + border: 1px solid #d1d5db; + border-radius: 8px; + box-sizing: border-box; + font-size: 14px; + transition: border-color 0.2s, box-shadow 0.2s; + background-color: #fff; +} + /* Inputs e selects */ .input-field, .select-field, textarea { - width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 8px; @@ -269,6 +331,18 @@ textarea { min-height: 80px; } +.financeiro-wrap .input-field:not(.modal-card *), +.financeiro-wrap .select-field:not(.modal-card *), +.financeiro-wrap textarea:not(.modal-card *) { + width: 30%; +} + +.modal-card .input-field, +.modal-card .select-field, +.modal-card textarea { + width: 100%; +} + /* Mensagem quando não há pagamentos */ .empty { text-align: center; @@ -366,4 +440,4 @@ html[data-bs-theme="dark"] textarea:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25); outline: none; -} \ No newline at end of file +} diff --git a/src/pages/style/Inicio.css b/src/pages/style/Inicio.css index 21e4058..a7da5d5 100644 --- a/src/pages/style/Inicio.css +++ b/src/pages/style/Inicio.css @@ -70,10 +70,10 @@ } /* Cores dos ícones */ -.stat-icon-wrapper.blue { background-color: #5d5dff; } -.stat-icon-wrapper.green { background-color: #30d158; } -.stat-icon-wrapper.purple { background-color: #a272ff; } -.stat-icon-wrapper.orange { background-color: #f1952e; } +.stat-icon-wrapper.blue { background-color: #1D3B88; } +.stat-icon-wrapper.green { background-color: #399CE5; } +.stat-icon-wrapper.purple { background-color: #5F5DF2; } +.stat-icon-wrapper.orange { background-color: #051AFF; } /* Seção de Ações Rápidas */ .quick-actions h2 {