diff --git a/package-lock.json b/package-lock.json index bb50c8d1..a672c1de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "express": "^5.1.0", "firebase": "^12.5.0", "flatpickr": "^4.6.13", + "helmet": "^8.1.0", "html2pdf.js": "^0.12.1", "lucide-react": "^0.543.0", "node-fetch": "^3.3.2", @@ -25979,6 +25980,15 @@ "he": "bin/he" } }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", diff --git a/package.json b/package.json index b12170f7..d9aad38c 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "express": "^5.1.0", "firebase": "^12.5.0", "flatpickr": "^4.6.13", + "helmet": "^8.1.0", "html2pdf.js": "^0.12.1", "lucide-react": "^0.543.0", "node-fetch": "^3.3.2", diff --git a/src/PagesPaciente/ConsultasPaciente.jsx b/src/PagesPaciente/ConsultasPaciente.jsx index 24a4811c..45de7a08 100644 --- a/src/PagesPaciente/ConsultasPaciente.jsx +++ b/src/PagesPaciente/ConsultasPaciente.jsx @@ -1,429 +1,371 @@ import React, { useState, useMemo, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import API_KEY from '../components/utils/apiKeys.js'; -import AgendamentoCadastroManager from '../pages/AgendamentoCadastroManager.jsx'; +import AgendamentoCadastroManager from '../pages/AgendamentoCadastroManager.jsx'; 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 { ChevronLeft, ChevronRight, Edit, Trash2 } from 'lucide-react'; -import "../pages/style/Agendamento.css"; +import localeData from 'dayjs/plugin/localeData'; +import { ChevronLeft, ChevronRight, Edit, Trash2 } 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); - +dayjs.extend(localeData); const Agendamento = ({ setDictInfo }) => { - const navigate = useNavigate(); - const { getAuthorizationHeader, user } = useAuth(); + const navigate = useNavigate(); + const { getAuthorizationHeader, user } = useAuth(); + const [isLoading, setIsLoading] = useState(true); + const [DictAgendamentosOrganizados, setDictAgendamentosOrganizados] = useState({}); + const [filaEsperaData, setFilaDeEsperaData] = useState([]); + const [FiladeEspera, setFiladeEspera] = useState(false); + const [PageNovaConsulta, setPageConsulta] = useState(false); - - const [isLoading, setIsLoading] = useState(true); - const [DictAgendamentosOrganizados, setDictAgendamentosOrganizados] = useState({}); - - - const [filaEsperaData, setFilaDeEsperaData] = useState([]); - - const [FiladeEspera, setFiladeEspera] = useState(false); - const [PageNovaConsulta, setPageConsulta] = useState(false); - + const [currentDate, setCurrentDate] = useState(dayjs()); + const [selectedDay, setSelectedDay] = useState(dayjs()); + const [quickJump, setQuickJump] = useState({ + month: currentDate.month(), + year: currentDate.year() + }); - const [currentDate, setCurrentDate] = useState(dayjs()); - const [selectedDay, setSelectedDay] = useState(dayjs()); - const [quickJump, setQuickJump] = useState({ - month: currentDate.month(), - year: currentDate.year() + const [isCancelModalOpen, setIsCancelModalOpen] = useState(false); + const [appointmentToCancel, setAppointmentToCancel] = useState(null); + const [cancellationReason, setCancellationReason] = useState(''); + + const authHeader = useMemo(() => getAuthorizationHeader(), [getAuthorizationHeader]); + + useEffect(() => { + const carregarDados = async () => { + const patientId = user?.patient_id || "6e7f8829-0574-42df-9290-8dbb70f75ada"; + + if (!authHeader) { + console.warn("Header de autorização não disponível."); + setIsLoading(false); + return; + } + + setIsLoading(true); + try { + const myHeaders = new Headers({ "Authorization": authHeader, "apikey": API_KEY }); + const requestOptions = { method: 'GET', headers: myHeaders }; + const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select=*,doctors(full_name)&patient_id=eq.${patientId}`, requestOptions); + + if (!response.ok) throw new Error(`Erro na requisição: ${response.statusText}`); + + const consultasBrutas = await response.json() || []; + + const newDict = {}; + const newFila = []; + + for (const agendamento of consultasBrutas) { + const agendamentoMelhorado = { + ...agendamento, + medico_nome: agendamento.doctors?.full_name || 'Médico não informado' + }; + + 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].push(agendamentoMelhorado); + } else { + newDict[diaAgendamento] = [agendamentoMelhorado]; + } + } + } + + for (const key in newDict) { + newDict[key].sort((a, b) => a.scheduled_at.localeCompare(b.scheduled_at)); + } + + setDictAgendamentosOrganizados(newDict); + setFilaDeEsperaData(newFila); + + } catch (err) { + console.error('Falha ao buscar ou processar agendamentos:', err); + setDictAgendamentosOrganizados({}); + setFilaDeEsperaData([]); + } finally { + setIsLoading(false); + } + }; + + carregarDados(); + }, [authHeader, user]); + + const updateAppointmentStatus = async (id, updates) => { + const myHeaders = new Headers({ + "Authorization": authHeader, "apikey": API_KEY, "Content-Type": "application/json" + }); + 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) throw new Error('Falha ao atualizar o status.'); + return true; + } catch (error) { + console.error('Erro de rede/servidor:', error); + return false; + } + }; + + const handleCancelClick = (appointmentId) => { + setAppointmentToCancel(appointmentId); + setCancellationReason(''); + setIsCancelModalOpen(true); + }; + + const executeCancellation = async () => { + if (!appointmentToCancel) return; + setIsLoading(true); + const motivo = cancellationReason.trim() || "Cancelado pelo paciente (motivo não especificado)"; + const success = await updateAppointmentStatus(appointmentToCancel, { + status: "cancelled", + cancellation_reason: motivo, + updated_at: new Date().toISOString() }); + setIsCancelModalOpen(false); + setAppointmentToCancel(null); + setCancellationReason(''); - - const [isCancelModalOpen, setIsCancelModalOpen] = useState(false); - const [appointmentToCancel, setAppointmentToCancel] = useState(null); - const [cancellationReason, setCancellationReason] = useState(''); - - const authHeader = useMemo(() => getAuthorizationHeader(), [getAuthorizationHeader]); + if (success) { + alert("Solicitação cancelada com sucesso!"); - - - useEffect(() => { - const carregarDados = async () => { - - const patientId = user?.patient_id || "6e7f8829-0574-42df-9290-8dbb70f75ada"; - - if (!authHeader) { - console.warn("Header de autorização não disponível."); - setIsLoading(false); - return; - } - - - setIsLoading(true); - try { - - const myHeaders = new Headers({ "Authorization": authHeader, "apikey": API_KEY }); - const requestOptions = { method: 'GET', headers: myHeaders }; - const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select=*,doctors(full_name)&patient_id=eq.${patientId}`, requestOptions); - - if (!response.ok) throw new Error(`Erro na requisição: ${response.statusText}`); - - const consultasBrutas = await response.json() || []; - - - - const newDict = {}; - const newFila = []; - - - for (const agendamento of consultasBrutas) { - const agendamentoMelhorado = { - ...agendamento, - medico_nome: agendamento.doctors?.full_name || 'Médico não informado' - }; - - 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].push(agendamentoMelhorado); - } else { - newDict[diaAgendamento] = [agendamentoMelhorado]; - } - } - } - - for (const key in newDict) { - newDict[key].sort((a, b) => a.scheduled_at.localeCompare(b.scheduled_at)); - } - - setDictAgendamentosOrganizados(newDict); - setFilaDeEsperaData(newFila); - - - } catch (err) { - console.error('Falha ao buscar ou processar agendamentos:', err); - setDictAgendamentosOrganizados({}); - setFilaDeEsperaData([]); - } finally { - setIsLoading(false); - } - }; - - - carregarDados(); - }, [authHeader, user]); - - - const updateAppointmentStatus = async (id, updates) => { - const myHeaders = new Headers({ - "Authorization": authHeader, "apikey": API_KEY, "Content-Type": "application/json" - }); - 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) throw new Error('Falha ao atualizar o status.'); - return true; - } catch (error) { - console.error('Erro de rede/servidor:', error); - return false; + setDictAgendamentosOrganizados(prev => { + const newDict = { ...prev }; + for (const date in newDict) { + newDict[date] = newDict[date].filter(app => app.id !== appointmentToCancel); } - }; - - - const handleCancelClick = (appointmentId) => { - setAppointmentToCancel(appointmentId); - setCancellationReason(''); - setIsCancelModalOpen(true); - }; - - - - const executeCancellation = async () => { - if (!appointmentToCancel) return; - - setIsLoading(true); - - - const motivo = cancellationReason.trim() || "Cancelado pelo paciente (motivo não especificado)"; - - const success = await updateAppointmentStatus(appointmentToCancel, { - status: "cancelled", - cancellation_reason: motivo, - updated_at: new Date().toISOString() - }); - - - setIsCancelModalOpen(false); - setAppointmentToCancel(null); - setCancellationReason(''); - - - if (success) { - alert("Solicitação cancelada com sucesso!"); - - setDictAgendamentosOrganizados(prev => { - const newDict = { ...prev }; - for (const date in newDict) { - newDict[date] = newDict[date].filter(app => app.id !== appointmentToCancel); - } - return newDict; - }); - setFilaDeEsperaData(prev => prev.filter(item => item.agendamento.id !== appointmentToCancel)); - } else { - alert("Falha ao cancelar a solicitação."); - } - setIsLoading(false); - }; - - - const handleQuickJumpChange = (type, value) => setQuickJump(prev => ({ ...prev, [type]: Number(value) })); - const applyQuickJump = () => { - const newDate = dayjs().year(quickJump.year).month(quickJump.month).date(1); - setCurrentDate(newDate); - setSelectedDay(newDate); - }; - const dateGrid = useMemo(() => { - 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; - }, [currentDate]); - const weekDays = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb']; - const handleDateClick = (day) => setSelectedDay(day); - - const activeButtonStyle = { - backgroundColor: '#1B2A41', - color: 'white', - padding: '6px 12px', - fontSize: '0.875rem', - borderRadius: '8px', - border: '1px solid white', - display: 'flex', - alignItems: 'center', - gap: '8px', - cursor: 'pointer' - }; - - const inactiveButtonStyle = { - backgroundColor: '#1B2A41', - color: 'white', - padding: '6px 12px', - fontSize: '0.875rem', - borderRadius: '8px', - border: '1px solid #1B2A41', - display: 'flex', - alignItems: 'center', - gap: '8px', - cursor: 'pointer' - }; - - - - if (isLoading) { - return ( -
{selectedDay.format('D [de] MMMM [de] YYYY')}
Nenhuma consulta agendada para esta data.
| Médico Solicitado | -Data da Solicitação | -Ações | -
|---|---|---|
| Dr(a). {item.Infos?.medico_nome} | -{dayjs(item.agendamento.created_at).format('DD/MM/YYYY HH:mm')} | -- - | -
|
- Nenhuma solicitação na fila de espera.
- |
- ||
{selectedDay.format('D [de] MMMM [de] YYYY')}
Nenhuma consulta agendada para esta data.
Qual o motivo do cancelamento?
- -| Médico Solicitado | +Data da Solicitação | +Ações | +
|---|---|---|
| Dr(a). {item.Infos?.medico_nome} | +{dayjs(item.agendamento.created_at).format('DD/MM/YYYY HH:mm')} | ++ + | +
|
+ Nenhuma solicitação na fila de espera.
+ |
+ ||
Qual o motivo do cancelamento?
+ +Tem certeza que deseja encerrar a sessão?
+Tem certeza que deseja encerrar a sessão?
-