From ffea77d911efc63b15d6816d6c5d495810d03661 Mon Sep 17 00:00:00 2001 From: jp-lima Date: Fri, 10 Oct 2025 13:15:30 -0300 Subject: [PATCH 01/14] POST de agendamentos --- .../AgendarConsulta/FormNovaConsulta.jsx | 211 +++++------------- .../utils/Functions-Endpoints/Doctor.js | 34 ++- src/pages/Agendamento.jsx | 66 +++++- 3 files changed, 148 insertions(+), 163 deletions(-) diff --git a/src/components/AgendarConsulta/FormNovaConsulta.jsx b/src/components/AgendarConsulta/FormNovaConsulta.jsx index 435bc8ee..d45e6596 100644 --- a/src/components/AgendarConsulta/FormNovaConsulta.jsx +++ b/src/components/AgendarConsulta/FormNovaConsulta.jsx @@ -1,57 +1,20 @@ import InputMask from "react-input-mask"; import "./style/formagendamentos.css"; import { useState, useEffect } from "react"; +import { GetPatientByCPF } from "../utils/Functions-Endpoints/Patient"; +import { GetDoctorByName } from "../utils/Functions-Endpoints/Doctor"; +import { useAuth } from "../utils/AuthProvider"; +const FormNovaConsulta = ({ onCancel, onSave }) => { + const {getAuthorizationHeader} = useAuth() -const FormNovaConsulta = ({ onCancel, patientID }) => { - const [selectedFile, setSelectedFile] = useState(null); const [anexos, setAnexos] = useState([]); const [loadingAnexos, setLoadingAnexos] = useState(false); - const [paciente, setPaciente] = useState({}) + const [agendamento, setAgendamento] = useState({}) const [acessibilidade, setAcessibilidade] = useState({cadeirante:false,idoso:false,gravida:false,bebe:false, autista:false }) - useEffect(() => { - if (!patientID) return; - - const fetchAnexos = async () => { - setLoadingAnexos(true); - try { - const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientID}/anexos`); - const data = await res.json(); - setAnexos(data.data || []); - } catch (err) { - console.error("Erro ao buscar anexos:", err); - } finally { - setLoadingAnexos(false); - } - }; - - fetchAnexos(); - }, [patientID]); - - const handleUpload = async () => { - if (!selectedFile) return; - - const formData = new FormData(); - formData.append("file", selectedFile); - - try { - const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientID}/anexos`, { - method: "POST", - body: formData - }); - if (res.ok) { - const novoAnexo = await res.json(); - setAnexos(prev => [...prev, novoAnexo]); - setSelectedFile(null); - } else { - console.error("Erro ao enviar anexo"); - } - } catch (err) { - console.error("Erro ao enviar anexo:", err); - } - }; + let authHeader = getAuthorizationHeader() const handleclickAcessibilidade = (id) => { @@ -62,112 +25,68 @@ const FormNovaConsulta = ({ onCancel, patientID }) => { else if(resultado === true){ setAcessibilidade({...acessibilidade, [id]:false})} console.log(id) } - - const FormatCPF = (valor) => { - console.log(valor) + const FormatCPF = (valor) => { const digits = String(valor).replace(/\D/g, '').slice(0, 11); - BuscarPacienteExistentePeloCPF(valor) - return digits .replace(/(\d{3})(\d)/, '$1.$2') .replace(/(\d{3})(\d)/, '$1.$2') .replace(/(\d{3})(\d{1,2})$/, '$1-$2'); } - - const FormatTelefones = (valor) => { - const digits = String(valor).replace(/\D/g, '').slice(0, 11); - return digits - .replace(/(\d)/, '($1') - .replace(/(\d{2})(\d)/, '$1) $2' ) - .replace(/(\d)(\d{4})/, '$1 $2') - .replace(/(\d{4})(\d{4})/, '$1-$2') - } - - - const BuscarCPFnoBancodeDados = async (cpf) => { - - var myHeaders = new Headers(); - myHeaders.append("Authorization", "Bearer "); - myHeaders.append("Content-Type", "application/json"); - - var raw = JSON.stringify({ - "cpf": cpf - }); - - var requestOptions = { - method: 'POST', - headers: myHeaders, - body: raw, - redirect: 'follow' - }; - - const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes/validar-cpf", requestOptions); - const result = await response.json(); - return result - - - } - - const BuscarPacienteExistentePeloCPF = async (value) => { - - if(isNaN(value[13]) === false && value.length === 14)try { - const result = await BuscarCPFnoBancodeDados(value); - console.log("Resultado:", result); - - if (result.data.existe === true){ - - var myHeaders = new Headers(); - myHeaders.append("Authorization", "Bearer "); - - var requestOptions = { - method: 'GET', - headers: myHeaders, - redirect: 'follow' - }; - - fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes/", requestOptions) - .then(response => response.json()) - .then(result => setPaciente(result.data)) - .catch(error => console.log('error', error)); - } - - - - } catch (error) { - console.log("error", error); - } - //BuscarCPFnoBancodeDados(value) - } + const handleChange = (e) => { const {value, name} = e.target; - console.log(value, name) + console.log(value, name, agendamento) if(name === 'email'){ - setPaciente({...paciente, contato:{ - ...paciente.contato, + setAgendamento({...agendamento, contato:{ + ...agendamento.contato, email:value }}) - } else if(name === 'telefone'){ - setPaciente({...paciente, contato:{ - ...paciente.contato, - telefone1:FormatTelefones(value) - }}) + }else if(name === 'cpf'){ + + let cpfFormatted = FormatCPF(value) + const fetchPatient = async () => { + let patientData = await GetPatientByCPF(cpfFormatted, authHeader); + if (patientData) { + setAgendamento((prev) => ({ + ...prev, + nome: patientData.full_name, + patient_id: patientData.id + })); + }} + setAgendamento(prev => ({ ...prev, cpf: cpfFormatted })) + fetchPatient() + }else if(name==='convenio'){ + setAgendamento({...agendamento,insurance_provider:value}) + }else if(name ==='profissional'){ + + + const fetchDoctor = async () => { + let DoctorData = await GetDoctorByName(value, authHeader) + if(DoctorData){ + setAgendamento((prev) => ({ + ...prev, + doctor_id:DoctorData.id + })) + }} + fetchDoctor() } else{ - setPaciente({...paciente,[name]:value}) + setAgendamento({...agendamento,[name]:value}) } } const handleSubmit = (e) => { e.preventDefault(); alert("Agendamento salvo!"); + onSave(agendamento) }; return ( @@ -180,57 +99,33 @@ const FormNovaConsulta = ({ onCancel, patientID }) => {
- +
- e.target.value = FormatCPF(e.target.value)} /> +
-
- - -
+
-
-
- - -
- -
- - -
- -
- - -
-
+
- + + +
-
- - -
- -
- - -
+

Informações adicionais

@@ -243,7 +138,7 @@ const FormNovaConsulta = ({ onCancel, patientID }) => { onChange={(e) => setSelectedFile(e.target.files[0])} /> {selectedFile && ( - )} @@ -291,7 +186,7 @@ const FormNovaConsulta = ({ onCancel, patientID }) => {
- +
diff --git a/src/components/utils/Functions-Endpoints/Doctor.js b/src/components/utils/Functions-Endpoints/Doctor.js index 2d6046f9..0aa4f437 100644 --- a/src/components/utils/Functions-Endpoints/Doctor.js +++ b/src/components/utils/Functions-Endpoints/Doctor.js @@ -23,4 +23,36 @@ return DictMedico } -export {GetDoctorByID} \ No newline at end of file +const GetAllDoctors = async (authHeader) => { + var myHeaders = new Headers(); + myHeaders.append("apikey", API_KEY); + myHeaders.append("Authorization", authHeader); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + const result = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions) + const DictMedicos = await result.json() + return DictMedicos + } + + +const GetDoctorByName = async (nome, authHeader) => { + const Medicos = await GetAllDoctors(authHeader) + + for (let i = 0; i < Medicos.length; i++) { + + if (Medicos[i].full_name === nome) { + console.log('Medico encontrado:', Medicos[i]); + return Medicos[i]; + } + else{console.log("nada encontrado")} + } + + +} + +export {GetDoctorByID, GetDoctorByName} \ No newline at end of file diff --git a/src/pages/Agendamento.jsx b/src/pages/Agendamento.jsx index d693cfee..c99ef275 100644 --- a/src/pages/Agendamento.jsx +++ b/src/pages/Agendamento.jsx @@ -1,23 +1,81 @@ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; +import API_KEY from '../components/utils/apiKeys.js'; import TabelaAgendamentoDia from '../components/AgendarConsulta/TabelaAgendamentoDia'; import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana'; import TabelaAgendamentoMes from '../components/AgendarConsulta/TabelaAgendamentoMes'; import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta'; - +import { useAuth } from '../components/utils/AuthProvider.js'; // ✨ NOVO: Caminho de importação corrigido com base na sua estrutura de pastas import AgendamentosMes from '../components/AgendarConsulta/DadosConsultasMock.js'; + import dayjs from 'dayjs'; import "./style/Agendamento.css"; import './style/FilaEspera.css'; const Agendamento = () => { - const [FiladeEspera, setFiladeEspera] = useState(false); + const [FiladeEspera, setFiladeEspera] = useState(false); const [tabela, setTabela] = useState('diario'); const [PageNovaConsulta, setPageConsulta] = useState(false); const [searchTerm, setSearchTerm] = useState(''); + const [agendamentos, setAgendamentos] = useState() + const {getAuthorizationHeader} = useAuth() + + let authHeader = getAuthorizationHeader() + + const handleSave = (Dict) => { + let DataAtual = dayjs() + var myHeaders = new Headers(); + myHeaders.append("apikey", API_KEY); + myHeaders.append("Authorization", authHeader); + myHeaders.append("Content-Type", "application/json"); + +var raw = JSON.stringify({ + "patient_id": Dict.patient_id, + "doctor_id": Dict.doctor_id, + "scheduled_at": DataAtual, + "duration_minutes": 30, + "appointment_type": "presencial", + "chief_complaint": "Dor de cabeça há 3 ", + "patient_notes": "Prefiro horário pela manhã", + "insurance_provider": "Unimed", + "created_by": "87f2662c-9da7-45c0-9e05-521d9d92d105" +}); + +var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' +}; + +fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOptions) + .then(response => response.text()) + .then(result => console.log(result)) + .catch(error => console.log('error', error)); + + } + + // Requisição inicial para mnostrar os agendamentos do banco de dados + useEffect(() => { + var myHeaders = new Headers(); + myHeaders.append("Authorization", authHeader); + myHeaders.append("apikey", API_KEY) + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions) + .then(response => response.json()) + .then(result => setAgendamentos(result)) + .catch(error => console.log('error', error)); + }, []) + // Dados da fila de espera (sem alteração) const filaEsperaData = [ @@ -216,7 +274,7 @@ const Agendamento = () => { ) : ( - + )} ) From b949971a28e2578a3144c844e01f8dbb0793caef Mon Sep 17 00:00:00 2001 From: jp-lima Date: Fri, 10 Oct 2025 15:48:08 -0300 Subject: [PATCH 02/14] =?UTF-8?q?Come=C3=A7o=20da=20cria=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20mostrar=20as=20consultas=20separadas=20por=20dias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Agendamento.jsx | 137 +++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/src/pages/Agendamento.jsx b/src/pages/Agendamento.jsx index c99ef275..2c85001b 100644 --- a/src/pages/Agendamento.jsx +++ b/src/pages/Agendamento.jsx @@ -16,15 +16,148 @@ import './style/FilaEspera.css'; const Agendamento = () => { + // Dados mocados para simular consultas com a API de Hugo + const Agendamentos = [ + { + id: "9d5c7233-0437-4283-84cd-668714b1c6af", + order_number: "APT-2025-0001", + patient_id: "47902ada-7d04-480a-a759-bae8a211973a", + appointment_type: "presencial", + cancellation_reason: null, + cancelled_at: null, + checked_in_at: null, + chief_complaint: "Dor de cabeça há 3", + completed_at: null, + created_at: "2025-10-10T15:56:59.112231+00:00", + created_by: "87f2662c-9da7-45c0-9e05-521d9d92d105", + doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + duration_minutes: 30, + insurance_provider: "Unimed", + notes: null, + patient_notes: "Prefiro horário pela manhã", + scheduled_at: "2025-10-10T15:56:58.937+00:00", + status: "requested", + updated_at: "2025-10-10T15:56:59.112231+00:00", + updated_by: null + }, + // mesma data + { + id: "c8d87a3a-f221-4e88-a71e-947c03f3a9c2", + order_number: "APT-2025-0002", + patient_id: "4b102ada-7d04-480a-a759-bae8a211973b", + appointment_type: "presencial", + chief_complaint: "Retorno de consulta", + created_at: "2025-10-10T09:30:00.000+00:00", + scheduled_at: "2025-10-10T09:30:00.000+00:00", + doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + duration_minutes: 45, + insurance_provider: "Amil", + status: "requested" + }, + // dia anterior 1 + { + id: "a3b77a12-1f11-45f8-912b-99372cd6a711", + order_number: "APT-2025-0003", + patient_id: "57902ada-7d04-480a-a759-bae8a211973a", + appointment_type: "teleconsulta", + chief_complaint: "Tosse persistente", + created_at: "2025-10-09T10:15:00.000+00:00", + scheduled_at: "2025-10-09T10:15:00.000+00:00", + doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + duration_minutes: 20, + insurance_provider: "Bradesco Saúde", + status: "confirmed" + }, + // dia anterior 2 + { + id: "f77a42e1-b1a3-41af-8b4f-b92e8372bb20", + order_number: "APT-2025-0004", + patient_id: "5a902ada-7d04-480a-a759-bae8a211973a", + appointment_type: "presencial", + chief_complaint: "Check-up anual", + created_at: "2025-10-09T14:00:00.000+00:00", + scheduled_at: "2025-10-09T14:00:00.000+00:00", + doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + duration_minutes: 60, + insurance_provider: "Unimed", + status: "requested" + }, + // dia seguinte + { + id: "b832a7e3-7319-4c22-b17b-922a6d4a9287", + order_number: "APT-2025-0005", + patient_id: "6c902ada-7d04-480a-a759-bae8a211973a", + appointment_type: "presencial", + chief_complaint: "Dor lombar", + created_at: "2025-10-11T11:45:00.000+00:00", + scheduled_at: "2025-10-11T11:45:00.000+00:00", + doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + duration_minutes: 30, + insurance_provider: "SulAmérica", + status: "requested" + }, + // outro qualquer (para variedade) + { + id: "d112f76a-24a1-41de-8d03-9e8a20a73b54", + order_number: "APT-2025-0006", + patient_id: "78902ada-7d04-480a-a759-bae8a211973a", + appointment_type: "teleconsulta", + chief_complaint: "Acompanhamento pós-cirurgia", + created_at: "2025-10-10T18:00:00.000+00:00", + scheduled_at: "2025-10-10T18:00:00.000+00:00", + doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + duration_minutes: 25, + insurance_provider: "Unimed", + status: "requested" + } +]; + + + const [FiladeEspera, setFiladeEspera] = useState(false); const [tabela, setTabela] = useState('diario'); const [PageNovaConsulta, setPageConsulta] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [agendamentos, setAgendamentos] = useState() const {getAuthorizationHeader} = useAuth() + const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({}) + + const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState() + let authHeader = getAuthorizationHeader() + + // id do doutor + // dias + + const FiltrarAgendamentos = (listaTodosAgendamentos) => { + + + // cria um dicionário temporário + let DictAgendamentosOrganizados = {}; + + for (let i = 0; i < listaTodosAgendamentos.length; i++) { + const agendamento = listaTodosAgendamentos[i]; + const DiaAgendamento = agendamento.scheduled_at.split("T")[0]; + + console.log(DictAgendamentosOrganizados) + + if (DiaAgendamento in DictAgendamentosOrganizados) { + // já existe a data → adiciona na lista + DictAgendamentosOrganizados[DiaAgendamento].push(agendamento); + } else { + // não existe → cria nova key com uma lista + DictAgendamentosOrganizados[DiaAgendamento] = [agendamento]; + } + } + + // faz o set de uma vez só ✅ + setAgendamentosOrganizados(DictAgendamentosOrganizados); + + + } + const handleSave = (Dict) => { let DataAtual = dayjs() var myHeaders = new Headers(); @@ -58,7 +191,7 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOp } - // Requisição inicial para mnostrar os agendamentos do banco de dados + // Requisição inicial para mostrar os agendamentos do banco de dados useEffect(() => { var myHeaders = new Headers(); myHeaders.append("Authorization", authHeader); @@ -72,7 +205,7 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOp fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions) .then(response => response.json()) - .then(result => setAgendamentos(result)) + .then(result => {FiltrarAgendamentos(Agendamentos); console.log(Agendamentos)}) .catch(error => console.log('error', error)); }, []) From 97840ebed4b913136b45cadbfc95eb0b60f953a0 Mon Sep 17 00:00:00 2001 From: Jessica_Faro Date: Sun, 12 Oct 2025 15:40:07 -0300 Subject: [PATCH 03/14] =?UTF-8?q?mudan=C3=A7as2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/ProfilePage.jsx | 253 ++++++++++---------------------- src/pages/style/ProfilePage.css | 178 ++++++++++++++++++++++ 2 files changed, 255 insertions(+), 176 deletions(-) diff --git a/src/pages/ProfilePage.jsx b/src/pages/ProfilePage.jsx index ef09ef14..6d4f10b0 100644 --- a/src/pages/ProfilePage.jsx +++ b/src/pages/ProfilePage.jsx @@ -1,206 +1,107 @@ // src/pages/ProfilePage.jsx -import React, { useState } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; -// import { useAuth } from '../components/utils/AuthProvider'; // <-- NOVO: Se você puder importar isso. +import React, { useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import "./style/ProfilePage.css"; -// --- SIMULAÇÃO DE DADOS DE USUÁRIO --- -// COMO NÃO PODEMOS IMPORTAR O useAuth SEM MEXER EM OUTROS ARQUIVOS, VAMOS USAR ESTA SIMULAÇÃO: const simulatedUserData = { - // ESTA SIMULAÇÃO DEVERIA SER SUBTITUÍDA PELO SEU CONTEXTO DE AUTENTICAÇÃO REAL. - // O EMAIL REALMENTE LOGADO VEM DO CONTEXTO DE AUTENTICAÇÃO (useAuth) - email: 'admin@squad23.com', - role: 'Administrador' // Vamos forçar um valor para fins de visualização + email: "admin@squad23.com", + role: "Administrador", }; const ProfilePage = () => { const location = useLocation(); const navigate = useNavigate(); - // const { user } = useAuth(); // Descomente esta linha e comente o bloco simulatedUserData se puder usar o useAuth! - // --- Lógica de Cargo (AGORA CORRIGIDA PARA PEGAR DA URL SE O CONTEXTO FALHAR) --- const getRoleFromPath = () => { const path = location.pathname; - if (path.includes('/admin')) return 'Administrador'; - if (path.includes('/secretaria')) return 'Secretária'; - if (path.includes('/medico')) return 'Médico'; - if (path.includes('/financeiro')) return 'Financeiro'; - return 'Usuário Padrão'; + if (path.includes("/admin")) return "Administrador"; + if (path.includes("/secretaria")) return "Secretária"; + if (path.includes("/medico")) return "Médico"; + if (path.includes("/financeiro")) return "Financeiro"; + return "Usuário Padrão"; }; - - // Use a simulação ou o dado real: - const userRole = simulatedUserData.role || getRoleFromPath(); - const userEmail = simulatedUserData.email || 'email.nao.encontrado@mediconnect.com'; - - // --- Estados do Componente --- - // Se o nome do usuário vier do contexto de autenticação, use-o aqui - const [userName, setUserName] = useState('Admin Padrão'); + const userRole = simulatedUserData.role || getRoleFromPath(); + const userEmail = simulatedUserData.email || "email.nao.encontrado@example.com"; + + const [userName, setUserName] = useState("Admin Padrão"); const [isEditingName, setIsEditingName] = useState(false); - // --- Funções de Interação --- - - const handleNameChange = (e) => { - if (e.key === 'Enter') { - setIsEditingName(false); - } + const handleNameKeyDown = (e) => { + if (e.key === "Enter") setIsEditingName(false); }; - const handleClose = () => { - navigate(-1); // Volta para a página anterior - }; + const handleClose = () => navigate(-1); return ( -
-
- - {/* Botão de Fechar (X) */} - - {/* 1. Área da Foto de Perfil (Quadrada) */} -
-
-
- -
- - {/* 2. Nome do Usuário com Edição */} -
- {isEditingName ? ( - setUserName(e.target.value)} - onBlur={() => setIsEditingName(false)} - onKeyPress={handleNameChange} - autoFocus - style={styles.nameInput} - /> - ) : ( -

{userName}

- )} - - +
+
+
+
+ +
- {/* 3. Email (AGORA EXIBE O VALOR SIMULADO/CORRIGIDO) */} -

Email: {userEmail}

+
+
+ {isEditingName ? ( + setUserName(e.target.value)} + onBlur={() => setIsEditingName(false)} + onKeyDown={handleNameKeyDown} + autoFocus + /> + ) : ( +

{userName}

+ )} - {/* 4. Cargo (AGORA EXIBE O VALOR SIMULADO/CORRIGIDO) */} -

Cargo: {userRole}

+ +
+ +

+ Email: {userEmail} +

+ +

+ Cargo: {userRole} +

+ +
+ +
+
); }; -// Estilos Atualizados para Aumentar o Tamanho do Modal -const styles = { - overlay: { - position: 'fixed', - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: 'rgba(0, 0, 0, 0.5)', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - zIndex: 1100, - }, - modalContainer: { - position: 'relative', - padding: '50px 70px', // Aumentado o padding - backgroundColor: 'white', - borderRadius: '15px', - boxShadow: '0 8px 30px rgba(0, 0, 0, 0.3)', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - minWidth: '400px', - width: '60%', // Aumentando a largura para cobrir mais a tela - maxWidth: '500px', // Limite máximo para não ficar gigante em telas grandes - height: 'auto', - }, - closeButton: { - position: 'absolute', - top: '15px', - right: '20px', - background: 'none', - border: 'none', - fontSize: '30px', - cursor: 'pointer', - color: '#666', - lineHeight: '1', - }, - // ... (Os estilos de profilePictureContainer, infoContainer, etc., permanecem iguais) - profilePictureContainer: { - width: '120px', - height: '120px', - borderRadius: '15px', - overflow: 'hidden', - boxShadow: '0 4px 10px rgba(0, 0, 0, 0.15)', - marginBottom: '20px', - }, - profilePicturePlaceholder: { - width: '100%', - height: '100%', - backgroundColor: '#A9A9A9', - backgroundImage: 'url(\'data:image/svg+xml;utf8,\')', - backgroundSize: '80%', - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center', - }, - infoContainer: { - textAlign: 'center', - maxWidth: '400px', - width: '100%', - }, - nameSection: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - marginBottom: '10px', - }, - userName: { - margin: '0 5px 0 0', - fontSize: '1.8rem', - color: '#333', - }, - nameInput: { - fontSize: '1.8rem', - padding: '5px', - border: '1px solid #ccc', - borderRadius: '5px', - textAlign: 'center', - marginRight: '5px', - }, - editButton: { - background: 'none', - border: 'none', - cursor: 'pointer', - fontSize: '1.2rem', - marginLeft: '5px', - }, - emailText: { - fontSize: '1rem', - color: '#666', - margin: '5px 0', - }, - roleText: { - fontSize: '1.1rem', - color: '#333', - marginTop: '15px', - paddingTop: '10px', - borderTop: '1px solid #eee', - } -}; - -export default ProfilePage; \ No newline at end of file +export default ProfilePage; diff --git a/src/pages/style/ProfilePage.css b/src/pages/style/ProfilePage.css index e69de29b..c9215aa2 100644 --- a/src/pages/style/ProfilePage.css +++ b/src/pages/style/ProfilePage.css @@ -0,0 +1,178 @@ +/* src/pages/ProfilePage.css */ + +/* Overlay que cobre toda a tela */ +.profile-overlay { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.65); + display: flex; + align-items: center; + justify-content: center; + z-index: 20000; /* acima de header, vlibras, botões de acessibilidade */ + padding: 20px; + box-sizing: border-box; +} + +/* Card central (estilo modal amplo parecido com a 4ª foto) */ +.profile-modal { + background: #ffffff; + border-radius: 10px; + padding: 18px; + width: min(1100px, 96%); + max-width: 1100px; + box-shadow: 0 18px 60px rgba(0, 0, 0, 0.5); + position: relative; + box-sizing: border-box; + overflow: visible; +} + +/* Botão fechar (X) no canto do card */ +.profile-close { + position: absolute; + top: 14px; + right: 14px; + background: none; + border: none; + font-size: 26px; + color: #666; + cursor: pointer; + line-height: 1; +} + +/* Conteúdo dividido em 2 colunas: esquerda avatar / direita infos */ +.profile-content { + display: flex; + gap: 28px; + align-items: flex-start; + padding: 22px 18px; +} + +/* Coluna esquerda - avatar */ +.profile-left { + width: 220px; + display: flex; + justify-content: center; +} + +/* Avatar quadrado com sombra (estilo da foto 4) */ +.avatar-wrapper { + position: relative; + width: 180px; + height: 180px; +} + +.avatar-square { + width: 100%; + height: 100%; + border-radius: 8px; + background-color: #d0d0d0; + background-image: url("data:image/svg+xml;utf8,"); + background-position: center; + background-repeat: no-repeat; + background-size: 55%; + box-shadow: 0 8px 24px rgba(0,0,0,0.25); +} + +/* Botão editar sobre o avatar — círculo pequeno */ +.avatar-edit-btn { + position: absolute; + right: -8px; + bottom: -8px; + transform: translate(0, 0); + border: none; + background: #ffffff; + padding: 8px 9px; + border-radius: 50%; + box-shadow: 0 6px 14px rgba(0,0,0,0.18); + cursor: pointer; + font-size: 0.95rem; + line-height: 1; +} + +/* Coluna direita - informações */ +.profile-right { + flex: 1; + min-width: 280px; + display: flex; + flex-direction: column; + justify-content: center; +} + +/* Nome e botão de editar inline */ +.profile-name-row { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; +} + +.profile-username { + margin: 0; + font-size: 1.9rem; + color: #222; +} + +.profile-edit-inline { + background: none; + border: none; + cursor: pointer; + font-size: 1.05rem; + color: #444; +} + +/* input de edição do nome */ +.profile-name-input { + font-size: 1.6rem; + padding: 6px 8px; + border: 1px solid #e0e0e0; + border-radius: 6px; +} + +/* email/role */ +.profile-email, +.profile-role { + margin: 6px 0; + color: #555; + font-size: 1rem; +} + +.profile-role { + margin-top: 14px; + padding-top: 12px; + border-top: 1px solid #f1f1f1; + color: #333; +} + +/* ações (apenas fechar aqui) */ +.profile-actions-row { + display: flex; + gap: 12px; + margin-top: 18px; +} + +/* botões */ +.btn { + padding: 8px 14px; + border-radius: 8px; + border: 1px solid transparent; + cursor: pointer; + font-size: 0.95rem; +} + +.btn-close { + background: #f0f0f0; + color: #222; + border: 1px solid #e6e6e6; +} + +/* responsividade */ +@media (max-width: 880px) { + .profile-content { + flex-direction: column; + gap: 14px; + align-items: center; + } + .profile-left { width: 100%; } + .avatar-wrapper { width: 140px; height: 140px; } + .profile-right { width: 100%; text-align: center; } +} From 39a78db8c5c0eb7f8ca7a5a0292a4276f884d7ee Mon Sep 17 00:00:00 2001 From: jp-lima Date: Mon, 13 Oct 2025 15:49:38 -0300 Subject: [PATCH 04/14] =?UTF-8?q?Fun=C3=A7=C3=A3o=20do=20agendamento=20GET?= =?UTF-8?q?=20para=20semana?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AgendarConsulta/CardConsulta.jsx | 26 ++- .../AgendarConsulta/TabelaAgendamentoDia.jsx | 24 +- .../TabelaAgendamentoSemana.jsx | 206 +++++++++++++++--- .../utils/Functions-Endpoints/Doctor.js | 2 - .../utils/Functions-Endpoints/Patient.js | 2 - src/pages/Agendamento.jsx | 65 +++--- src/pages/DoctorTable.jsx | 2 +- src/pages/PatientCadastroManager.jsx | 1 - src/pages/TablePaciente.jsx | 4 +- 9 files changed, 253 insertions(+), 79 deletions(-) diff --git a/src/components/AgendarConsulta/CardConsulta.jsx b/src/components/AgendarConsulta/CardConsulta.jsx index 250a534d..f0cdd463 100644 --- a/src/components/AgendarConsulta/CardConsulta.jsx +++ b/src/components/AgendarConsulta/CardConsulta.jsx @@ -1,10 +1,24 @@ -import React from 'react' - +import React, { useState, useEffect } from 'react';import { GetDoctorByID } from '../utils/Functions-Endpoints/Doctor'; +import { GetPatientByID } from '../utils/Functions-Endpoints/Patient'; +import { useAuth } from '../utils/AuthProvider'; const CardConsulta = ( {DadosConsulta, TabelaAgendamento} ) => { + const {getAuthorizationHeader} = useAuth() + const authHeader = getAuthorizationHeader() + const [Paciente, setPaciente] = useState() + const [Medico, setMedico] = useState() - + const BuscarMedicoEPaciente = async () => { + const Doctor = await GetDoctorByID(DadosConsulta.doctor_id, authHeader) + const Patient = await GetPatientByID(DadosConsulta.patient_id, authHeader) + setMedico(Doctor[0]) + setPaciente(Patient[0]) + console.log(Doctor, Patient) + } + // Status (agendado, confirmado, realizado, cancelado) - + useEffect(() => { + BuscarMedicoEPaciente() +}, []) return (
@@ -12,11 +26,11 @@ const CardConsulta = ( {DadosConsulta, TabelaAgendamento} ) => { {DadosConsulta.status !== 'vazio'?
-

{DadosConsulta.horario}|GEAP| {DadosConsulta.medico}

+

{DadosConsulta.horario}|GEAP| {Medico?.full_name}

-

{DadosConsulta.paciente} - {DadosConsulta.motivo} - 23 anos

+

{Paciente?.full_name} - {DadosConsulta.exam}

: diff --git a/src/components/AgendarConsulta/TabelaAgendamentoDia.jsx b/src/components/AgendarConsulta/TabelaAgendamentoDia.jsx index da73a5af..15cca203 100644 --- a/src/components/AgendarConsulta/TabelaAgendamentoDia.jsx +++ b/src/components/AgendarConsulta/TabelaAgendamentoDia.jsx @@ -1,14 +1,32 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import CardConsulta from './CardConsulta'; import "./style/styleTabelas/tabeladia.css"; - const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos }) => { + const [indiceAcesso, setIndiceAcesso] = useState(0) + const [Dia, setDia] = useState() const agendamentosDoDia = agendamentos?.semana1?.segunda || []; const nomeMedico = agendamentosDoDia.find(item => item.medico)?.medico || 'Profissional'; + let ListaDiasComAgendamentos = Object.keys(agendamentos) + + + + console.log(Dia, "hshdhshhsdhs") + + useEffect(() => { + setDia(ListaDiasComAgendamentos[indiceAcesso]) + }, [indiceAcesso]) + + + return (
+
+ +

{Dia}

+ +
@@ -18,7 +36,7 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos }) => { - {agendamentosDoDia.map((agendamento, index) => ( + {agendamentos[Dia]?.map((agendamento, index) => (

{agendamento.horario}

diff --git a/src/components/AgendarConsulta/TabelaAgendamentoSemana.jsx b/src/components/AgendarConsulta/TabelaAgendamentoSemana.jsx index 3f3c2b61..82f26bbf 100644 --- a/src/components/AgendarConsulta/TabelaAgendamentoSemana.jsx +++ b/src/components/AgendarConsulta/TabelaAgendamentoSemana.jsx @@ -1,31 +1,151 @@ import React from 'react'; import CardConsulta from './CardConsulta'; import "./style/styleTabelas/tabelasemana.css"; +import dayjs from 'dayjs'; +import { useEffect, useState, useMemo } from 'react'; +import weekOfYear from 'dayjs/plugin/weekOfYear' +dayjs.extend(weekOfYear) +const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes }) => { -const TabelaAgendamentoSemana = ({ agendamentos }) => { + // Armazena o objeto COMPLETO das semanas organizadas + const [semanasOrganizadas, setSemanasOrganizadas] = useState({}); + // Controla qual semana está sendo exibida (o índice da chave no objeto) + const [Indice, setIndice] = useState(0); + const dataHoje = dayjs(); + const AnoAtual = dataHoje.year(); + const mes = dataHoje.month() + 1; + + let DiasdoMes = ListarDiasdoMes(AnoAtual, mes) + + // Array de chaves (ex: ['semana40', 'semana41', ...]) + const chavesDasSemanas = Object.keys(semanasOrganizadas); + + // Armazena o total de semanas que foram organizadas (para definir os limites de navegação) + const totalSemanas = chavesDasSemanas.length; + + // --- LÓGICA DE ORGANIZAÇÃO (useMemo mantido para otimização) --- + + const OrganizarAgendamentosSemanais = useMemo(() => { + if (!agendamentos || Object.keys(agendamentos).length === 0) return {}; + + const DiasComAtendimentos = Object.keys(agendamentos) + const semanas = {} + + for (let i = 0; i < DiasComAtendimentos.length; i++) { + const DiaComAtendimento = DiasComAtendimentos[i] + const [_, MesDoAgendamento, DiaDoAgendamento] = DiaComAtendimento.split("-") + + const data = dayjs(`${AnoAtual}-${MesDoAgendamento}-${DiaDoAgendamento}`) + const diaSemana = data.format('dddd') + const semanaKey = `semana${data.week()}` + + if (!semanas[semanaKey]) { + semanas[semanaKey] = { + segunda: [], terça: [], quarta: [], quinta: [], sexta: [] + } + } + + switch (diaSemana) { + case 'Monday': + semanas[semanaKey].segunda.push(...agendamentos[DiaComAtendimento]) + break + case 'Tuesday': + semanas[semanaKey].terça.push(...agendamentos[DiaComAtendimento]) + break + case 'Wednesday': + semanas[semanaKey].quarta.push(...agendamentos[DiaComAtendimento]) + break + case 'Thursday': + semanas[semanaKey].quinta.push(...agendamentos[DiaComAtendimento]) + break + case 'Friday': + semanas[semanaKey].sexta.push(...agendamentos[DiaComAtendimento]) + break + default: + break + } + } + + return semanas + }, [agendamentos, AnoAtual]) // Adicionei AnoAtual como dependência por segurança + + // --- EFEITO PARA POPULAR O ESTADO --- + + useEffect(() => { + setSemanasOrganizadas(OrganizarAgendamentosSemanais); + // NOTA: Ao carregar, o Indice é 0, que é a primeira semana. + }, [OrganizarAgendamentosSemanais]) + + // --- NOVAS FUNÇÕES DE NAVEGAÇÃO --- + + const avancarSemana = () => { + // Avança se o índice atual não for o último (totalSemanas - 1) + if (Indice < totalSemanas - 1) { + setIndice(Indice + 1); + } + }; + + const voltarSemana = () => { + // Volta se o índice atual não for o primeiro (0) + if (Indice > 0) { + setIndice(Indice - 1); + } + }; - const agendamentoSemana = agendamentos?.semana1 || {}; - - const agendamentosDeSegunda = agendamentoSemana.segunda || []; - const agendamentosDeTerca = agendamentoSemana.terca || []; - const agendamentosDeQuarta = agendamentoSemana.quarta || []; - const agendamentosDeQuinta = agendamentoSemana.quinta || []; - const agendamentosDeSexta = agendamentoSemana.sexta || []; + // --- PREPARAÇÃO DOS DADOS PARA RENDERIZAÇÃO --- - + // Pega a chave da semana que deve ser exibida (usa o estado Indice) + const chaveDaSemanaAtual = chavesDasSemanas[Indice]; + + // Extrai os agendamentos da semana atual (ou um objeto vazio se não existir) + const semanaParaRenderizar = semanasOrganizadas[chaveDaSemanaAtual] || { + segunda: [], terça: [], quarta: [], quinta: [], sexta: [] + }; + + // Determina o número máximo de linhas/consultas const numLinhas = Math.max( - agendamentosDeSegunda.length, - agendamentosDeTerca.length, - agendamentosDeQuarta.length, - agendamentosDeQuinta.length, - agendamentosDeSexta.length + semanaParaRenderizar.segunda.length, + semanaParaRenderizar.terça.length, + semanaParaRenderizar.quarta.length, + semanaParaRenderizar.quinta.length, + semanaParaRenderizar.sexta.length ); + // Array de índices para iterar sobre as LINHAS da tabela + const indicesDeLinha = Array.from({ length: numLinhas }, (_, i) => i); + + // Título da semana (para mostrar ao usuário) + const tituloSemana = chaveDaSemanaAtual + ? `Semana ${chaveDaSemanaAtual.replace('semana', '')} / ${AnoAtual}` + : 'Nenhuma semana encontrada'; + + // --- RENDERIZAÇÃO --- return (
+ {/* Container de Navegação */} +
+ + + +

{tituloSemana}

+ + +
+ + {/* Tabela de Agendamentos */} @@ -38,28 +158,44 @@ const TabelaAgendamentoSemana = ({ agendamentos }) => { - {Array.from({ length: numLinhas }).map((_, index) => { - - const consultaSeg = agendamentosDeSegunda[index]; - const consultaTer = agendamentosDeTerca[index]; - const consultaQua = agendamentosDeQuarta[index]; - const consultaQui = agendamentosDeQuinta[index]; - const consultaSex = agendamentosDeSexta[index]; + {indicesDeLinha.map((indiceLinha) => ( + + {/* Célula para Horário (Pode ser ajustado para mostrar o horário real) */} + - - const horarioDaLinha = consultaSeg?.horario || consultaTer?.horario || consultaQua?.horario || consultaQui?.horario || consultaSex?.horario; - - return ( - - - - - - - - - ); - })} + {/* Mapeamento de COLUNAS (dias) */} + + + + + + + ))}
{horarioDaLinha}{consultaSeg && }{consultaTer && }{consultaQua && }{consultaQui && }{consultaSex && }
+ {semanaParaRenderizar.segunda[indiceLinha] + ? + : null + } + + {semanaParaRenderizar.terça[indiceLinha] + ? + : null + } + + {semanaParaRenderizar.quarta[indiceLinha] + ? + : null + } + + {semanaParaRenderizar.quinta[indiceLinha] + ? + : null + } + + {semanaParaRenderizar.sexta[indiceLinha] + ? + : null + } +
diff --git a/src/components/utils/Functions-Endpoints/Doctor.js b/src/components/utils/Functions-Endpoints/Doctor.js index 0aa4f437..d3c0e28e 100644 --- a/src/components/utils/Functions-Endpoints/Doctor.js +++ b/src/components/utils/Functions-Endpoints/Doctor.js @@ -4,8 +4,6 @@ import API_KEY from "../apiKeys"; const GetDoctorByID = async (ID,authHeader) => { - console.log(authHeader, 'mostrando autorização dentro da função') - var myHeaders = new Headers(); myHeaders.append('apikey', API_KEY) myHeaders.append('Authorization', authHeader) diff --git a/src/components/utils/Functions-Endpoints/Patient.js b/src/components/utils/Functions-Endpoints/Patient.js index ad848545..14cbfd6c 100644 --- a/src/components/utils/Functions-Endpoints/Patient.js +++ b/src/components/utils/Functions-Endpoints/Patient.js @@ -4,8 +4,6 @@ import API_KEY from "../apiKeys"; const GetPatientByID = async (ID,authHeader) => { - console.log(authHeader, 'mostrando autorização dentro da função') - var myHeaders = new Headers(); myHeaders.append('apikey', API_KEY) myHeaders.append('Authorization', authHeader) diff --git a/src/pages/Agendamento.jsx b/src/pages/Agendamento.jsx index 2c85001b..cc0bebf7 100644 --- a/src/pages/Agendamento.jsx +++ b/src/pages/Agendamento.jsx @@ -19,9 +19,9 @@ const Agendamento = () => { // Dados mocados para simular consultas com a API de Hugo const Agendamentos = [ { - id: "9d5c7233-0437-4283-84cd-668714b1c6af", + id: "", order_number: "APT-2025-0001", - patient_id: "47902ada-7d04-480a-a759-bae8a211973a", + patient_id: "a8039e6d-7271-4187-a719-e27d9c6d15b3", appointment_type: "presencial", cancellation_reason: null, cancelled_at: null, @@ -30,7 +30,7 @@ const Agendamento = () => { completed_at: null, created_at: "2025-10-10T15:56:59.112231+00:00", created_by: "87f2662c-9da7-45c0-9e05-521d9d92d105", - doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + doctor_id: "c2715ddb-e8fb-4319-8015-4fd5df93a39b", duration_minutes: 30, insurance_provider: "Unimed", notes: null, @@ -42,74 +42,85 @@ const Agendamento = () => { }, // mesma data { - id: "c8d87a3a-f221-4e88-a71e-947c03f3a9c2", + id: "", order_number: "APT-2025-0002", - patient_id: "4b102ada-7d04-480a-a759-bae8a211973b", + patient_id: "becd4c18-042e-44ad-9bcb-cfef08f18046", appointment_type: "presencial", chief_complaint: "Retorno de consulta", created_at: "2025-10-10T09:30:00.000+00:00", scheduled_at: "2025-10-10T09:30:00.000+00:00", - doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + doctor_id: "c2715ddb-e8fb-4319-8015-4fd5df93a39b", duration_minutes: 45, insurance_provider: "Amil", status: "requested" }, // dia anterior 1 { - id: "a3b77a12-1f11-45f8-912b-99372cd6a711", + id: "", order_number: "APT-2025-0003", - patient_id: "57902ada-7d04-480a-a759-bae8a211973a", + patient_id: "e17e2bc6-6a90-4dc6-ae2d-b503e2835d36", appointment_type: "teleconsulta", chief_complaint: "Tosse persistente", created_at: "2025-10-09T10:15:00.000+00:00", scheduled_at: "2025-10-09T10:15:00.000+00:00", - doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + doctor_id: "c2715ddb-e8fb-4319-8015-4fd5df93a39b", duration_minutes: 20, insurance_provider: "Bradesco Saúde", status: "confirmed" }, // dia anterior 2 { - id: "f77a42e1-b1a3-41af-8b4f-b92e8372bb20", + id: "", order_number: "APT-2025-0004", - patient_id: "5a902ada-7d04-480a-a759-bae8a211973a", + patient_id: "d20e418f-6e45-495a-98be-16a9a163fab3", appointment_type: "presencial", chief_complaint: "Check-up anual", created_at: "2025-10-09T14:00:00.000+00:00", scheduled_at: "2025-10-09T14:00:00.000+00:00", - doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + doctor_id: "c2715ddb-e8fb-4319-8015-4fd5df93a39b", duration_minutes: 60, insurance_provider: "Unimed", status: "requested" }, // dia seguinte { - id: "b832a7e3-7319-4c22-b17b-922a6d4a9287", + id: "", order_number: "APT-2025-0005", - patient_id: "6c902ada-7d04-480a-a759-bae8a211973a", + patient_id: "8f27e87d-851a-484a-8450-6e3a5f29476c", appointment_type: "presencial", chief_complaint: "Dor lombar", created_at: "2025-10-11T11:45:00.000+00:00", - scheduled_at: "2025-10-11T11:45:00.000+00:00", - doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + scheduled_at: "2025-10-08T11:45:00.000+00:00", + doctor_id: "9471fb52-4de6-4052-b173-1f0695173ba3", duration_minutes: 30, insurance_provider: "SulAmérica", status: "requested" }, // outro qualquer (para variedade) { - id: "d112f76a-24a1-41de-8d03-9e8a20a73b54", + id: "", order_number: "APT-2025-0006", - patient_id: "78902ada-7d04-480a-a759-bae8a211973a", + patient_id: "47902ada-7d04-480a-a759-bae8a211973a", appointment_type: "teleconsulta", chief_complaint: "Acompanhamento pós-cirurgia", created_at: "2025-10-10T18:00:00.000+00:00", scheduled_at: "2025-10-10T18:00:00.000+00:00", - doctor_id: "16e93000-7239-4865-85f6-7f89af3ce5cd", + doctor_id: "9471fb52-4de6-4052-b173-1f0695173ba3", duration_minutes: 25, insurance_provider: "Unimed", status: "requested" - } + }, { id: "", + order_number: "APT-2025-0006", + patient_id: "47902ada-7d04-480a-a759-bae8a211973a", + appointment_type: "teleconsulta", + chief_complaint: "Acompanhamento pós-cirurgia", + created_at: "2025-10-10T18:00:00.000+00:00", + scheduled_at: "2025-10-24T18:00:00.000+00:00", + doctor_id: "9471fb52-4de6-4052-b173-1f0695173ba3", + duration_minutes: 25, + insurance_provider: "Unimed", + status: "requested"} + ]; @@ -155,7 +166,6 @@ const Agendamento = () => { // faz o set de uma vez só ✅ setAgendamentosOrganizados(DictAgendamentosOrganizados); - } const handleSave = (Dict) => { @@ -190,9 +200,9 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOp .catch(error => console.log('error', error)); } - // Requisição inicial para mostrar os agendamentos do banco de dados useEffect(() => { + var myHeaders = new Headers(); myHeaders.append("Authorization", authHeader); myHeaders.append("apikey", API_KEY) @@ -205,11 +215,12 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOp fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions) .then(response => response.json()) - .then(result => {FiltrarAgendamentos(Agendamentos); console.log(Agendamentos)}) + .then(result => {FiltrarAgendamentos(Agendamentos); console.log(Agendamentos, "aqui")}) .catch(error => console.log('error', error)); }, []) + // Dados da fila de espera (sem alteração) const filaEsperaData = [ { nome: 'Ricardo Pereira', email: 'ricardo.pereira@gmail.com', cpf: '444.777.666-55', telefone: '(79) 99123-4567', entrada: '25/09/2025 às 08:00' }, @@ -267,7 +278,7 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOp default: break } } - let ListaDiasDatas = [segundas, tercas, quartas, quintas, sextas] + let ListaDiasDatas = {segundas:segundas,tercas:tercas,quartas: quartas,quintas: quintas,sextas: sextas} return ListaDiasDatas } @@ -360,9 +371,9 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOp - {tabela === "diario" && } - {tabela === 'semanal' && } - {tabela === 'mensal' && } + {tabela === "diario" && } + {tabela === 'semanal' && } + {tabela === 'mensal' && } ) diff --git a/src/pages/DoctorTable.jsx b/src/pages/DoctorTable.jsx index 019f7f74..47d9aacb 100644 --- a/src/pages/DoctorTable.jsx +++ b/src/pages/DoctorTable.jsx @@ -71,7 +71,7 @@ function TableDoctor() { fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions) .then(response => response.json()) - .then(result => setMedicos(result)) + .then(result => {setMedicos(result); console.log(result)}) .catch(error => console.log('error', error)); }, []); diff --git a/src/pages/PatientCadastroManager.jsx b/src/pages/PatientCadastroManager.jsx index 701e6b5b..4672f813 100644 --- a/src/pages/PatientCadastroManager.jsx +++ b/src/pages/PatientCadastroManager.jsx @@ -171,7 +171,6 @@ function PatientCadastroManager( {setCurrentPage} ) { }); setShowModal(true); - setTimeout(() => { setShowModal(false); navigate('/secretaria/pacientes'); diff --git a/src/pages/TablePaciente.jsx b/src/pages/TablePaciente.jsx index a82e63d1..4386b1dc 100644 --- a/src/pages/TablePaciente.jsx +++ b/src/pages/TablePaciente.jsx @@ -102,7 +102,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) { const authHeader = getAuthorizationHeader() - console.log(authHeader, 'aqui autorização') + console.log(authHeader) var myHeaders = new Headers(); myHeaders.append("apikey", API_KEY); @@ -115,7 +115,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) { fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions) .then(response => response.json()) - .then(result => setPacientes(result)) + .then(result => {setPacientes(result); console.log(result)}) .catch(error => console.log('error', error)); }, [isAuthenticated, getAuthorizationHeader]); From 320efb3d11b66be401b7b48d185ae7a2a2cad245 Mon Sep 17 00:00:00 2001 From: pedrofedericoo Date: Tue, 14 Oct 2025 21:19:18 -0300 Subject: [PATCH 05/14] Adiciona funcionalidade da API de Disponibilidade --- .../AgendarConsulta/FormNovaConsulta.jsx | 455 +++++------- src/components/doctors/DoctorForm.jsx | 680 +++++++++++++----- src/pages/DoctorCadastroManager.jsx | 1 + src/pages/DoctorEditPage.jsx | 164 +++-- 4 files changed, 783 insertions(+), 517 deletions(-) diff --git a/src/components/AgendarConsulta/FormNovaConsulta.jsx b/src/components/AgendarConsulta/FormNovaConsulta.jsx index 435bc8ee..1e7fb54f 100644 --- a/src/components/AgendarConsulta/FormNovaConsulta.jsx +++ b/src/components/AgendarConsulta/FormNovaConsulta.jsx @@ -2,22 +2,32 @@ import InputMask from "react-input-mask"; import "./style/formagendamentos.css"; import { useState, useEffect } from "react"; - -const FormNovaConsulta = ({ onCancel, patientID }) => { - +const FormNovaConsulta = ({ onCancel, patientID, profissionalId }) => { const [selectedFile, setSelectedFile] = useState(null); const [anexos, setAnexos] = useState([]); const [loadingAnexos, setLoadingAnexos] = useState(false); - const [paciente, setPaciente] = useState({}) - const [acessibilidade, setAcessibilidade] = useState({cadeirante:false,idoso:false,gravida:false,bebe:false, autista:false }) + const [paciente, setPaciente] = useState({}); + const [acessibilidade, setAcessibilidade] = useState({ + cadeirante: false, + idoso: false, + gravida: false, + bebe: false, + autista: false, + }); - useEffect(() => { + const [disponibilidades, setDisponibilidades] = useState([]); + const [horarioSelecionado, setHorarioSelecionado] = useState(""); + + // Buscar anexos do paciente + useEffect(() => { if (!patientID) return; const fetchAnexos = async () => { setLoadingAnexos(true); try { - const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientID}/anexos`); + const res = await fetch( + `https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientID}/anexos` + ); const data = await res.json(); setAnexos(data.data || []); } catch (err) { @@ -30,6 +40,49 @@ const FormNovaConsulta = ({ onCancel, patientID }) => { fetchAnexos(); }, [patientID]); + // Buscar disponibilidades do médico + useEffect(() => { + if (!profissionalId) return; + + const fetchDisponibilidades = async () => { + try { + const res = await fetch( + `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability?doctorId=${profissionalId}` + ); + const data = await res.json(); + setDisponibilidades(data.data || []); + } catch (err) { + console.error("Erro ao buscar disponibilidades:", err); + } + }; + + fetchDisponibilidades(); + }, [profissionalId]); + + // Função para criar intervalos de 30 minutos + const gerarIntervalos = (inicio, fim) => { + const horarios = []; + let horaAtual = new Date(`1970-01-01T${inicio}:00`); + const horaFim = new Date(`1970-01-01T${fim}:00`); + + while (horaAtual < horaFim) { + const proximo = new Date(horaAtual.getTime() + 30 * 60000); + if (proximo <= horaFim) { + horarios.push( + `${horaAtual.toTimeString().slice(0, 5)} - ${proximo + .toTimeString() + .slice(0, 5)}` + ); + } + horaAtual = proximo; + } + return horarios; + }; + + const intervalosDisponiveis = disponibilidades.flatMap((disp) => + gerarIntervalos(disp.horarioInicial, disp.horarioFinal) + ); + const handleUpload = async () => { if (!selectedFile) return; @@ -37,13 +90,16 @@ const FormNovaConsulta = ({ onCancel, patientID }) => { formData.append("file", selectedFile); try { - const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientID}/anexos`, { - method: "POST", - body: formData - }); + const res = await fetch( + `https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientID}/anexos`, + { + method: "POST", + body: formData, + } + ); if (res.ok) { const novoAnexo = await res.json(); - setAnexos(prev => [...prev, novoAnexo]); + setAnexos((prev) => [...prev, novoAnexo]); setSelectedFile(null); } else { console.error("Erro ao enviar anexo"); @@ -51,202 +107,116 @@ const FormNovaConsulta = ({ onCancel, patientID }) => { } catch (err) { console.error("Erro ao enviar anexo:", err); } - }; - - - const handleclickAcessibilidade = (id) => { - let resultado = acessibilidade[id] - - if(resultado === false){ setAcessibilidade({...acessibilidade, [id]:true}); console.log('mudou')} - - else if(resultado === true){ setAcessibilidade({...acessibilidade, [id]:false})} - console.log(id) - } - - - const FormatCPF = (valor) => { - console.log(valor) - - const digits = String(valor).replace(/\D/g, '').slice(0, 11); - BuscarPacienteExistentePeloCPF(valor) - - return digits - .replace(/(\d{3})(\d)/, '$1.$2') - .replace(/(\d{3})(\d)/, '$1.$2') - .replace(/(\d{3})(\d{1,2})$/, '$1-$2'); - } - - - const FormatTelefones = (valor) => { - const digits = String(valor).replace(/\D/g, '').slice(0, 11); - return digits - .replace(/(\d)/, '($1') - .replace(/(\d{2})(\d)/, '$1) $2' ) - .replace(/(\d)(\d{4})/, '$1 $2') - .replace(/(\d{4})(\d{4})/, '$1-$2') - } - - - const BuscarCPFnoBancodeDados = async (cpf) => { - - var myHeaders = new Headers(); - myHeaders.append("Authorization", "Bearer "); - myHeaders.append("Content-Type", "application/json"); - - var raw = JSON.stringify({ - "cpf": cpf - }); - - var requestOptions = { - method: 'POST', - headers: myHeaders, - body: raw, - redirect: 'follow' }; - const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes/validar-cpf", requestOptions); - const result = await response.json(); - return result + const handleclickAcessibilidade = (id) => { + setAcessibilidade({ + ...acessibilidade, + [id]: !acessibilidade[id], + }); + }; - - } + const FormatCPF = (valor) => { + const digits = String(valor).replace(/\D/g, "").slice(0, 11); - const BuscarPacienteExistentePeloCPF = async (value) => { - - if(isNaN(value[13]) === false && value.length === 14)try { - const result = await BuscarCPFnoBancodeDados(value); - console.log("Resultado:", result); - - if (result.data.existe === true){ + return digits + .replace(/(\d{3})(\d)/, "$1.$2") + .replace(/(\d{3})(\d)/, "$1.$2") + .replace(/(\d{3})(\d{1,2})$/, "$1-$2"); + }; - var myHeaders = new Headers(); - myHeaders.append("Authorization", "Bearer "); - - var requestOptions = { - method: 'GET', - headers: myHeaders, - redirect: 'follow' - }; - - fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes/", requestOptions) - .then(response => response.json()) - .then(result => setPaciente(result.data)) - .catch(error => console.log('error', error)); - } - - - - } catch (error) { - console.log("error", error); - } - //BuscarCPFnoBancodeDados(value) - } + const FormatTelefones = (valor) => { + const digits = String(valor).replace(/\D/g, "").slice(0, 11); + return digits + .replace(/(\d)/, "($1") + .replace(/(\d{2})(\d)/, "$1) $2") + .replace(/(\d)(\d{4})/, "$1 $2") + .replace(/(\d{4})(\d{4})/, "$1-$2"); + }; const handleChange = (e) => { + const { value, name } = e.target; - const {value, name} = e.target; - - console.log(value, name) - - if(name === 'email'){ - setPaciente({...paciente, contato:{ - ...paciente.contato, - email:value - }}) - - } else if(name === 'telefone'){ - setPaciente({...paciente, contato:{ - ...paciente.contato, - telefone1:FormatTelefones(value) - }}) + if (name === "email") { + setPaciente({ + ...paciente, + contato: { + ...paciente.contato, + email: value, + }, + }); + } else if (name === "telefone") { + setPaciente({ + ...paciente, + contato: { + ...paciente.contato, + telefone1: FormatTelefones(value), + }, + }); + } else { + setPaciente({ ...paciente, [name]: value }); } - else{ - setPaciente({...paciente,[name]:value}) - } - } + }; const handleSubmit = (e) => { e.preventDefault(); - alert("Agendamento salvo!"); + alert( + `Agendamento salvo! Horário selecionado: ${ + horarioSelecionado || "não selecionado" + }` + ); }; return (
- -

Informações do paciente

-
+ {/* Campos do paciente */} +
- +
- - e.target.value = FormatCPF(e.target.value)} /> - + (e.target.value = FormatCPF(e.target.value))} + />
- -
- - -
-
- -
-
- - -
- -
- - -
- -
- - -
-
- -
- -
- -
-
- - -
- -
- - -
-
+ {/* Informações adicionais / anexos */}

Informações adicionais

- - - setSelectedFile(e.target.files[0])} - /> - {selectedFile && ( - - )} + + setSelectedFile(e.target.files[0])} + /> + {selectedFile && ( + + )}
{loadingAnexos ? (

Carregando anexos...

@@ -258,42 +228,15 @@ const FormNovaConsulta = ({ onCancel, patientID }) => { )) )}
+ + {/* Informações do atendimento */}

Informações do atendimento

- - -
- -
handleclickAcessibilidade(e.currentTarget.id)}> - - accessible -
- -
handleclickAcessibilidade(e.currentTarget.id)}> - elderly -
- -
handleclickAcessibilidade(e.currentTarget.id)}> - pregnant_woman -
- -
handleclickAcessibilidade(e.currentTarget.id)}> - - -
- -
handleclickAcessibilidade(e.currentTarget.id)}> - -
- -
- -
- - -
- +
+ + +
@@ -301,67 +244,59 @@ const FormNovaConsulta = ({ onCancel, patientID }) => {
-
-
- -
-
- - -
- - - -
- - -
+ {/* Seção de datas e horários */} +
+
+ +
-
-
- - -
- -
- - -
- -
- - -
+
+ +
-
-
- +
+ + +
+
- - -
- - +
+ +
-
); }; -export default FormNovaConsulta; \ No newline at end of file +export default FormNovaConsulta; diff --git a/src/components/doctors/DoctorForm.jsx b/src/components/doctors/DoctorForm.jsx index 45ec0bf2..4de5b56f 100644 --- a/src/components/doctors/DoctorForm.jsx +++ b/src/components/doctors/DoctorForm.jsx @@ -1,37 +1,35 @@ -import React, { useState, useRef } from 'react'; -import { Link, useNavigate, useLocation } from 'react-router-dom'; +import React, { useState, useRef } from "react"; +import { Link, useNavigate, useLocation } from "react-router-dom"; function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { const navigate = useNavigate(); const location = useLocation(); const FormatTelefones = (valor) => { - const digits = String(valor).replace(/\D/g, '').slice(0, 11); + const digits = String(valor).replace(/\D/g, "").slice(0, 11); return digits - .replace(/(\d)/, '($1') - .replace(/(\d{2})(\d)/, '$1) $2') - .replace(/(\d)(\d{4})/, '$1 $2') - .replace(/(\d{4})(\d{4})/, '$1-$2'); + .replace(/(\d)/, "($1") + .replace(/(\d{2})(\d)/, "$1) $2") + .replace(/(\d)(\d{4})/, "$1 $2") + .replace(/(\d{4})(\d{4})/, "$1-$2"); }; const FormatCPF = (valor) => { - const digits = String(valor).replace(/\D/g, '').slice(0, 11); + const digits = String(valor).replace(/\D/g, "").slice(0, 11); return digits - .replace(/(\d{3})(\d)/, '$1.$2') - .replace(/(\d{3})(\d)/, '$1.$2') - .replace(/(\d{3})(\d{1,2})$/, '$1-$2'); + .replace(/(\d{3})(\d)/, "$1.$2") + .replace(/(\d{3})(\d)/, "$1.$2") + .replace(/(\d{3})(\d{1,2})$/, "$1-$2"); }; - const validarCPF = (cpf) => { - const cpfLimpo = cpf.replace(/\D/g, ''); - + const cpfLimpo = cpf.replace(/\D/g, ""); + if (cpfLimpo.length !== 11) return false; if (/^(\d)\1+$/.test(cpfLimpo)) return false; - - + let soma = 0; for (let i = 0; i < 9; i++) { soma += parseInt(cpfLimpo.charAt(i)) * (10 - i); @@ -46,15 +44,17 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { } resto = 11 - (soma % 11); let digito2 = resto === 10 || resto === 11 ? 0 : resto; - - - return digito1 === parseInt(cpfLimpo.charAt(9)) && digito2 === parseInt(cpfLimpo.charAt(10)); + + return ( + digito1 === parseInt(cpfLimpo.charAt(9)) && + digito2 === parseInt(cpfLimpo.charAt(10)) + ); }; const [avatarUrl, setAvatarUrl] = useState(null); const [showRequiredModal, setShowRequiredModal] = useState(false); const [emptyFields, setEmptyFields] = useState([]); - const [cpfError, setCpfError] = useState(''); + const [cpfError, setCpfError] = useState(""); const nomeRef = useRef(null); const cpfRef = useRef(null); @@ -70,63 +70,59 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { }); const handleToggleCollapse = (section) => { - setCollapsedSections(prevState => ({ + setCollapsedSections((prevState) => ({ ...prevState, - [section]: !prevState[section] + [section]: !prevState[section], })); }; const handleChange = (e) => { const { name, value, type, checked, files } = e.target; - if (value && emptyFields.includes(name)) { - setEmptyFields(prev => prev.filter(field => field !== name)); + setEmptyFields((prev) => prev.filter((field) => field !== name)); } - - if (name === 'cpf' && cpfError) { - setCpfError(''); + if (name === "cpf" && cpfError) { + setCpfError(""); } - if (type === 'checkbox') { - setFormData(prev => ({ ...prev, [name]: checked })); - } else if (type === 'file') { - setFormData(prev => ({ ...prev, [name]: files[0] })); + if (type === "checkbox") { + setFormData((prev) => ({ ...prev, [name]: checked })); + } else if (type === "file") { + setFormData((prev) => ({ ...prev, [name]: files[0] })); - if (name === 'foto' && files[0]) { + if (name === "foto" && files[0]) { const reader = new FileReader(); reader.onloadend = () => { setAvatarUrl(reader.result); }; reader.readAsDataURL(files[0]); - } else if (name === 'foto' && !files[0]) { + } else if (name === "foto" && !files[0]) { setAvatarUrl(null); } - - } else if (name.includes('cpf')) { + } else if (name.includes("cpf")) { let cpfFormatado = FormatCPF(value); - setFormData(prev => ({ ...prev, [name]: cpfFormatado })); + setFormData((prev) => ({ ...prev, [name]: cpfFormatado })); - - const cpfLimpo = cpfFormatado.replace(/\D/g, ''); + const cpfLimpo = cpfFormatado.replace(/\D/g, ""); if (cpfLimpo.length === 11) { if (!validarCPF(cpfFormatado)) { - setCpfError('CPF inválido'); + setCpfError("CPF inválido"); } else { - setCpfError(''); + setCpfError(""); } } - } else if (name.includes('phone')) { + } else if (name.includes("phone")) { let telefoneFormatado = FormatTelefones(value); - setFormData(prev => ({ ...prev, [name]: telefoneFormatado })); + setFormData((prev) => ({ ...prev, [name]: telefoneFormatado })); } else { - setFormData(prev => ({ ...prev, [name]: value })); + setFormData((prev) => ({ ...prev, [name]: value })); } }; const handleCepBlur = async () => { - const cep = formData.cep?.replace(/\D/g, ''); + const cep = formData.cep?.replace(/\D/g, ""); if (cep && cep.length === 8) { try { const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`); @@ -134,50 +130,49 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { if (!data.erro) { setFormData((prev) => ({ ...prev, - street: data.logradouro || '', - neighborhood: data.bairro || '', - city: data.localidade || '', - state: data.uf || '' + street: data.logradouro || "", + neighborhood: data.bairro || "", + city: data.localidade || "", + state: data.uf || "", })); } else { setShowRequiredModal(true); - setEmptyFields(['cep']); + setEmptyFields(["cep"]); } } catch (error) { setShowRequiredModal(true); - setEmptyFields(['cep']); + setEmptyFields(["cep"]); } } }; - const scrollToEmptyField = (fieldName) => { let fieldRef = null; - + switch (fieldName) { - case 'full_name': + case "full_name": fieldRef = nomeRef; - setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); break; - case 'cpf': + case "cpf": fieldRef = cpfRef; - setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); break; - case 'email': + case "email": fieldRef = emailRef; - setCollapsedSections(prev => ({ ...prev, contato: true })); + setCollapsedSections((prev) => ({ ...prev, contato: true })); break; - case 'phone_mobile': + case "phone_mobile": fieldRef = telefoneRef; - setCollapsedSections(prev => ({ ...prev, contato: true })); + setCollapsedSections((prev) => ({ ...prev, contato: true })); break; - case 'crm_uf': + case "crm_uf": fieldRef = crmUfRef; - setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); break; - case 'crm': + case "crm": fieldRef = crmRef; - setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); break; default: return; @@ -186,21 +181,20 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { setTimeout(() => { if (fieldRef.current) { - fieldRef.current.scrollIntoView({ - behavior: 'smooth', - block: 'center' + fieldRef.current.scrollIntoView({ + behavior: "smooth", + block: "center", }); fieldRef.current.focus(); - - - fieldRef.current.style.border = '2px solid #dc3545'; - fieldRef.current.style.boxShadow = '0 0 0 0.2rem rgba(220, 53, 69, 0.25)'; - - + + fieldRef.current.style.border = "2px solid #dc3545"; + fieldRef.current.style.boxShadow = + "0 0 0 0.2rem rgba(220, 53, 69, 0.25)"; + setTimeout(() => { if (fieldRef.current) { - fieldRef.current.style.border = ''; - fieldRef.current.style.boxShadow = ''; + fieldRef.current.style.border = ""; + fieldRef.current.style.boxShadow = ""; } }, 3000); } @@ -210,18 +204,17 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { const handleSubmit = async () => { const missingFields = []; - if (!formData.full_name) missingFields.push('full_name'); - if (!formData.cpf) missingFields.push('cpf'); - if (!formData.email) missingFields.push('email'); - if (!formData.phone_mobile) missingFields.push('phone_mobile'); - if (!formData.crm_uf) missingFields.push('crm_uf'); - if (!formData.crm) missingFields.push('crm'); + if (!formData.full_name) missingFields.push("full_name"); + if (!formData.cpf) missingFields.push("cpf"); + if (!formData.email) missingFields.push("email"); + if (!formData.phone_mobile) missingFields.push("phone_mobile"); + if (!formData.crm_uf) missingFields.push("crm_uf"); + if (!formData.crm) missingFields.push("crm"); if (missingFields.length > 0) { setEmptyFields(missingFields); setShowRequiredModal(true); - - + setTimeout(() => { if (missingFields.length > 0) { scrollToEmptyField(missingFields[0]); @@ -230,26 +223,23 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { return; } - - const cpfLimpo = formData.cpf.replace(/\D/g, ''); + const cpfLimpo = formData.cpf.replace(/\D/g, ""); if (cpfLimpo.length !== 11) { setShowRequiredModal(true); - setEmptyFields(['cpf']); - setCpfError('CPF deve ter 11 dígitos'); - setTimeout(() => scrollToEmptyField('cpf'), 500); + setEmptyFields(["cpf"]); + setCpfError("CPF deve ter 11 dígitos"); + setTimeout(() => scrollToEmptyField("cpf"), 500); return; } - if (!validarCPF(formData.cpf)) { setShowRequiredModal(true); - setEmptyFields(['cpf']); - setCpfError('CPF inválido'); - setTimeout(() => scrollToEmptyField('cpf'), 500); + setEmptyFields(["cpf"]); + setCpfError("CPF inválido"); + setTimeout(() => scrollToEmptyField("cpf"), 500); return; } - try { await onSave({ ...formData }); @@ -300,7 +290,16 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { alignItems: "center", }} > -
Atenção
+
+ Atenção +
- + - @@ -631,4 +848,79 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { ); } +function WeeklyAvailabilityPicker() { + const days = ["Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"]; + const [availability, setAvailability] = useState( + days.reduce((acc, day) => ({ ...acc, [day]: [] }), {}) + ); + + const handleAddInterval = (day) => { + const newIntervals = [...availability[day], { start: "", end: "" }]; + const newAvailability = { ...availability, [day]: newIntervals }; + setAvailability(newAvailability); + }; + + const handleRemoveInterval = (day, index) => { + const newIntervals = availability[day].filter((_, i) => i !== index); + setAvailability({ ...availability, [day]: newIntervals }); + }; + + const handleTimeChange = (day, index, field, value) => { + const newIntervals = availability[day].map((interval, i) => + i === index ? { ...interval, [field]: value } : interval + ); + setAvailability({ ...availability, [day]: newIntervals }); + }; + + const handleSave = () => { + const data = []; + for (const [day, intervals] of Object.entries(availability)) { + intervals.forEach(({ start, end }) => { + const dayIndex = days.indexOf(day); + data.push({ day: dayIndex, start, end }); + }); + } + + fetch("https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }) + .then((res) => res.json()) + .then((res) => console.log("Salvo:", res)) + .catch((err) => console.error("Erro:", err)); + }; + + return ( +
+ {days.map((day) => ( +
+
{day}
+ {availability[day].map((interval, index) => ( +
+ handleTimeChange(day, index, "start", e.target.value)} + /> + até + handleTimeChange(day, index, "end", e.target.value)} + /> + +
+ ))} + +
+ ))} + +
+ ); +} + export default DoctorForm; \ No newline at end of file diff --git a/src/pages/DoctorCadastroManager.jsx b/src/pages/DoctorCadastroManager.jsx index 450e7f75..eb97f566 100644 --- a/src/pages/DoctorCadastroManager.jsx +++ b/src/pages/DoctorCadastroManager.jsx @@ -44,6 +44,7 @@ function DoctorCadastroManager() { number: doctorData.number || null, complement: doctorData.complement || null, phone2: doctorData.phone2 ? doctorData.phone2.replace(/\D/g, '') : null, + availability: doctorData.availability || {}, }; console.log('Dados limpos para envio:', cleanedData); diff --git a/src/pages/DoctorEditPage.jsx b/src/pages/DoctorEditPage.jsx index eecfb147..d5bd701d 100644 --- a/src/pages/DoctorEditPage.jsx +++ b/src/pages/DoctorEditPage.jsx @@ -1,77 +1,115 @@ -import React from 'react' -import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor' -import DoctorForm from '../components/doctors/DoctorForm' -import { useAuth } from '../components/utils/AuthProvider' -import {useEffect, useState} from 'react' -import { useParams } from 'react-router-dom' -import API_KEY from '../components/utils/apiKeys' +import React, { useEffect, useState } from "react"; +import { GetDoctorByID } from "../components/utils/Functions-Endpoints/Doctor"; +import DoctorForm from "../components/doctors/DoctorForm"; +import { useAuth } from "../components/utils/AuthProvider"; +import { useParams } from "react-router-dom"; +import API_KEY from "../components/utils/apiKeys"; + const DoctorEditPage = () => { - const {getAuthorizationHeader, isAuthenticated} = useAuth(); - const [DoctorToPUT, setDoctorPUT] = useState({}) - - const Parametros = useParams() + const { getAuthorizationHeader } = useAuth(); + const [DoctorToPUT, setDoctorPUT] = useState({}); + const [availability, setAvailability] = useState([]); + const { id: DoctorID } = useParams(); - const DoctorID = Parametros.id + useEffect(() => { + const authHeader = getAuthorizationHeader(); -useEffect(() => { - - const authHeader = getAuthorizationHeader() + // Buscar médico + GetDoctorByID(DoctorID, authHeader) + .then((data) => setDoctorPUT(data[0])) + .catch((err) => console.error(err)); - GetDoctorByID(DoctorID, authHeader) - .then((data) => { - console.log(data, "médico vindo da API"); - setDoctorPUT(data[0]) - ; // supabase retorna array - }) - .catch((err) => console.error("Erro ao buscar paciente:", err)); + // Buscar disponibilidades + const fetchAvailability = async () => { + try { + const res = await fetch( + `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability/${DoctorID}`, + { headers: { apikey: API_KEY, Authorization: authHeader } } + ); + const data = await res.json(); + setAvailability(data.data || []); + } catch (err) { + console.error(err); + } + }; - -}, []) - const HandlePutDoctor = async () => { -const authHeader = getAuthorizationHeader() - + fetchAvailability(); + }, []); - var myHeaders = new Headers(); - myHeaders.append('apikey', API_KEY) - myHeaders.append("Authorization", authHeader); - myHeaders.append("Content-Type", "application/json"); - - var raw = JSON.stringify(DoctorToPUT); - - console.log("Enviando médico para atualização:", DoctorToPUT); - - var requestOptions = { - method: 'PUT', - headers: myHeaders, - body: raw, - redirect: 'follow' + // Atualizar uma disponibilidade + const updateAvailability = async (id, updatedData) => { + const authHeader = getAuthorizationHeader(); + try { + await fetch( + `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability/${id}`, + { + method: "PUT", + headers: { + apikey: API_KEY, + Authorization: authHeader, + "Content-Type": "application/json", + }, + body: JSON.stringify(updatedData), + } + ); + // atualizar localmente + setAvailability((prev) => + prev.map((a) => (a.id === id ? { ...a, ...updatedData } : a)) + ); + } catch (err) { + console.error(err); + } }; - try { - const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`,requestOptions); - console.log(response) - - } catch (error) { - console.error("Erro ao atualizar paciente:", error); - throw error; - } - - } + // Deletar uma disponibilidade + const deleteAvailability = async (id) => { + const authHeader = getAuthorizationHeader(); + try { + await fetch( + `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability/${id}`, + { + method: "DELETE", + headers: { apikey: API_KEY, Authorization: authHeader }, + } + ); + setAvailability((prev) => prev.filter((a) => a.id !== id)); + } catch (err) { + console.error(err); + } + }; + const HandlePutDoctor = async () => { + const authHeader = getAuthorizationHeader(); + try { + await fetch( + `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`, + { + method: "PUT", + headers: { + apikey: API_KEY, + Authorization: authHeader, + "Content-Type": "application/json", + }, + body: JSON.stringify(DoctorToPUT), + } + ); + } catch (err) { + console.error(err); + } + }; return (
- - - +
- ) -} + ); +}; -export default DoctorEditPage \ No newline at end of file +export default DoctorEditPage; From cc37ea60daee0206ff1f3af6a18ecd4d6c211bba Mon Sep 17 00:00:00 2001 From: jp-lima Date: Wed, 15 Oct 2025 09:34:32 -0300 Subject: [PATCH 06/14] =?UTF-8?q?Finaliza=C3=A7=C3=A3o=20das=20agendas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AgendarConsulta/CardConsulta.jsx | 8 +- .../AgendarConsulta/TabelaAgendamentoMes.jsx | 293 ++++++++++++------ .../style/styleTabelas/tabelames.css | 4 + 3 files changed, 201 insertions(+), 104 deletions(-) diff --git a/src/components/AgendarConsulta/CardConsulta.jsx b/src/components/AgendarConsulta/CardConsulta.jsx index f0cdd463..accb6ccc 100644 --- a/src/components/AgendarConsulta/CardConsulta.jsx +++ b/src/components/AgendarConsulta/CardConsulta.jsx @@ -17,16 +17,18 @@ const CardConsulta = ( {DadosConsulta, TabelaAgendamento} ) => { // Status (agendado, confirmado, realizado, cancelado) useEffect(() => { - BuscarMedicoEPaciente() + if(DadosConsulta.status !== 'nada'){BuscarMedicoEPaciente()} + }, []) + return ( -
+
console.log('Clicou n aconsulta')}> {DadosConsulta.status !== 'vazio'?
-

{DadosConsulta.horario}|GEAP| {Medico?.full_name}

+

{DadosConsulta.horario} {Medico?.full_name}

diff --git a/src/components/AgendarConsulta/TabelaAgendamentoMes.jsx b/src/components/AgendarConsulta/TabelaAgendamentoMes.jsx index 7e404084..616c4013 100644 --- a/src/components/AgendarConsulta/TabelaAgendamentoMes.jsx +++ b/src/components/AgendarConsulta/TabelaAgendamentoMes.jsx @@ -3,7 +3,8 @@ import React from 'react'; import dayjs from "dayjs"; import CardConsulta from './CardConsulta'; import "./style/styleTabelas/tabelames.css"; - +import { useEffect, useState } from 'react'; +import { useMemo } from 'react'; const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos }) => { @@ -12,15 +13,174 @@ const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos }) => { const mes = dataHoje.month() + 1; let ListaDiasDatas = ListarDiasdoMes(AnoAtual, mes); + const [AgendamentosSemanaisOrganizados, setAgendamentosSemanaisOrganizados] = useState({}) + const [indice, setIndice] = useState("10") - let segundas = ListaDiasDatas[0]; - let tercas = ListaDiasDatas[1]; - let quartas = ListaDiasDatas[2]; - let quintas = ListaDiasDatas[3]; - let sextas = ListaDiasDatas[4]; + const [AgendamentosMensaisOrganizados, setAgendamentosMensaisOrganizados] = useState({ + "01": { "nomeDoMes": "janeiro" }, + "02": { "nomeDoMes": "fevereiro" }, + "03": { "nomeDoMes": "março" }, + "04": { "nomeDoMes": "abril" }, + "05": { "nomeDoMes": "maio" }, + "06": { "nomeDoMes": "junho" }, + "07": { "nomeDoMes": "julho" }, + "08": { "nomeDoMes": "agosto" }, + "09": { "nomeDoMes": "setembro" }, + "10": { "nomeDoMes": "outubro" }, + "11": { "nomeDoMes": "novembro" }, + "12": { "nomeDoMes": "dezembro" } +}) + + + + + const OrganizarAgendamentosSemanais = useMemo(() => { + if (!agendamentos || Object.keys(agendamentos).length === 0) return {}; + + const DiasComAtendimentos = Object.keys(agendamentos) + const semanas = {} + + + for (let i = 0; i < DiasComAtendimentos.length; i++) { + const DiaComAtendimento = DiasComAtendimentos[i] + const [_, MesDoAgendamento, DiaDoAgendamento] = DiaComAtendimento.split("-") + + const data = dayjs(`${AnoAtual}-${MesDoAgendamento}-${DiaDoAgendamento}`) + const diaSemana = data.format('dddd') + const semanaKey = `semana${data.week()}` + + if (!semanas[semanaKey]) { + semanas[semanaKey] = { + segunda: [], terça: [], quarta: [], quinta: [], sexta: [] + } + } + + switch (diaSemana) { + case 'Monday': + semanas[semanaKey].segunda.push(...agendamentos[DiaComAtendimento]) + break + case 'Tuesday': + semanas[semanaKey].terça.push(...agendamentos[DiaComAtendimento]) + break + case 'Wednesday': + semanas[semanaKey].quarta.push(...agendamentos[DiaComAtendimento]) + break + case 'Thursday': + semanas[semanaKey].quinta.push(...agendamentos[DiaComAtendimento]) + break + case 'Friday': + semanas[semanaKey].sexta.push(...agendamentos[DiaComAtendimento]) + break + default: + break + } + } + + return semanas + }, [agendamentos, AnoAtual]) + + useEffect(() => { + setAgendamentosSemanaisOrganizados(OrganizarAgendamentosSemanais); + + + // NOTA: Ao carregar, o Indice é 0, que é a primeira semana. + }, [OrganizarAgendamentosSemanais]) + + useEffect(() => { + console.log(OrganizarAgendamentosMensais) + + + }, []) + + useEffect(() => { + console.log(AgendamentosMensaisOrganizados, 'aqui os agendamentos mensais') + }, [AgendamentosMensaisOrganizados]) + + const OrganizarAgendamentosMensais = useMemo(() => { + if (!AgendamentosSemanaisOrganizados || Object.keys(AgendamentosSemanaisOrganizados).length === 0) + return; + + // Cria uma cópia local do estado atual + const novoEstado = { ...AgendamentosMensaisOrganizados }; + + const indices = Object.keys(AgendamentosSemanaisOrganizados); + + for (let i = 0; i < indices.length; i++) { + const DictSemanais = AgendamentosSemanaisOrganizados[indices[i]]; + const indicesDictSemanais = Object.keys(DictSemanais); + + for (let d = 0; d < indicesDictSemanais.length; d++) { + const lista = DictSemanais[indicesDictSemanais[d]]; + + if (lista.length > 0) { + const [_, mesDaConsulta] = lista[0].scheduled_at.split("-"); + + // Cria o mês se ainda não existir + if (!novoEstado[mesDaConsulta]) { + novoEstado[mesDaConsulta] = { + nomeDoMes: AgendamentosMensaisOrganizados[mesDaConsulta]?.nomeDoMes || "", + }; + } + + // Garante que a semana existe + novoEstado[mesDaConsulta][indices[i]] = { + ...novoEstado[mesDaConsulta][indices[i]], + ...DictSemanais, + }; + } + } + } + + // Faz o set de uma vez só + setAgendamentosMensaisOrganizados(novoEstado); +}, [AgendamentosSemanaisOrganizados]); + + const AvançarMes = () => { + let Indice = parseInt(indice) + Indice += 1 + + console.log(Indice) + if(Indice < 10){ + Indice = "0" + Indice.toString() + console.log(Indice) + } + if(Indice === 13){ + return + }else{ + setIndice(Indice) + } + } + + const VoltarMes = () => { + let Indice = parseInt(indice) + + Indice -= 1 + + console.log(Indice) + if(Indice < 10){ + Indice = "0" + Indice.toString() + console.log(Indice) + } + if(Indice === "00"){ + return + }else{ + + setIndice(Indice) + } + + + } + return (
+
+ +

{AgendamentosMensaisOrganizados[indice].nomeDoMes}

+ + +
+ @@ -32,105 +192,36 @@ const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos }) => { - {agendamentos && Object.entries(agendamentos).map(([semana, dias], index) => ( - - {/* Coluna de Segunda-feira */} - + {Object.keys(AgendamentosMensaisOrganizados[indice]).map((semanaKey) => { + const semana = AgendamentosMensaisOrganizados[indice][semanaKey] + console.log(AgendamentosMensaisOrganizados[indice][semanaKey], 'ajdsahchbaohdfoduh') - {/* Coluna de Terça-feira */} - + - {/* Coluna de Quarta-feira */} - + - {/* Coluna de Quinta-feira */} - + return( + + + { + semana && typeof semana === "object" && Object.keys(semana).map((dia) => ( + - - ))} - + ): null } + + )) + } + + + )})} +
-
-

{segundas[index]}

-
- {(dias.segunda || []).slice(0, 3).map((consulta, idx) => ( - - ))} -
- {(dias.segunda || []).length > 3 ? -

+ {(dias.segunda || []).length - 3}

- : null} -
-
-
-

{tercas[index]}

-
- {(dias.terca || []).slice(0, 3).map((consulta, idx) => ( - - ))} -
- {(dias.terca || []).length > 3 ? -

+ {(dias.terca || []).length - 3}

- : null} -
-
-
-

{quartas[index]}

-
- {(dias.quarta || []).slice(0, 3).map((consulta, idx) => ( - - ))} -
- {(dias.quarta || []).length > 3 ? -

+ {(dias.quarta || []).length - 3}

- : null} -
-
-
-

{quintas[index]}

-
- {(dias.quinta || []).slice(0, 3).map((consulta, idx) => ( - - ))} -
- {(dias.quinta || []).length > 3 ? -

+ {(dias.quinta || []).length - 3}

- : null} -
-
console.log('Clicou n aconsulta')}> + + + + {semana[dia].length > 3 ? ( +
+

{` +${semana[dia].length - 2}`}

+
- {/* Coluna de Sexta-feira */} -
-
-

{sextas[index]}

-
- {(dias.sexta || []).slice(0, 3).map((consulta, idx) => ( - - ))} -
- {(dias.sexta || []).length > 3 ? -

+ {(dias.sexta || []).length - 3}

- : null} -
-
) diff --git a/src/components/AgendarConsulta/style/styleTabelas/tabelames.css b/src/components/AgendarConsulta/style/styleTabelas/tabelames.css index 5fbe05c0..b63f5e14 100644 --- a/src/components/AgendarConsulta/style/styleTabelas/tabelames.css +++ b/src/components/AgendarConsulta/style/styleTabelas/tabelames.css @@ -219,4 +219,8 @@ html[data-bs-theme="dark"] .usuario-default { html[data-bs-theme="dark"] .cards-que-faltam { color: #90caf9; +} + +th{ + color: white; } \ No newline at end of file From 1c4512511c2d2e761e96fbc166d40283a6064cec Mon Sep 17 00:00:00 2001 From: GilenoNeto901 Date: Wed, 15 Oct 2025 15:17:56 -0300 Subject: [PATCH 07/14] perfil paciente --- src/App.js | 3 ++- src/components/TrocardePerfis.jsx | 1 + src/data/sidebar-items-paciente.json | 13 +++++++++++ src/perfis/Perfil_paciente/Perfilpaciente.jsx | 23 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/data/sidebar-items-paciente.json create mode 100644 src/perfis/Perfil_paciente/Perfilpaciente.jsx diff --git a/src/App.js b/src/App.js index 13120035..efa60255 100644 --- a/src/App.js +++ b/src/App.js @@ -9,7 +9,7 @@ import LandingPage from './pages/LandingPage'; import PerfilFinanceiro from "./perfis/perfil_financeiro/PerfilFinanceiro"; import Perfiladm from "./perfis/Perfil_adm/Perfiladm"; import PerfilMedico from "./perfis/Perfil_medico/PerfilMedico"; - +import PerfilPaciente from "./perfis/Perfil_paciente/Perfilpaciente" // Componentes globais de acessibilidade import VlibrasWidget from "./components/VlibrasWidget"; @@ -30,6 +30,7 @@ function App() { } /> } /> } /> + } /> Página não encontrada} /> diff --git a/src/components/TrocardePerfis.jsx b/src/components/TrocardePerfis.jsx index d340b7fc..87b24828 100644 --- a/src/components/TrocardePerfis.jsx +++ b/src/components/TrocardePerfis.jsx @@ -33,6 +33,7 @@ const TrocardePerfis = () => { { key: "medico", label: "Médico", route: "/medico" }, { key: "financeiro", label: "Financeiro", route: "/financeiro" }, { key: "admin", label: "Administração", route: "/admin" }, + { key: "paciente", label: "Paciente", route: "/paciente" }, ].filter( (opt) => showProfiles?.includes(opt.key) || showProfiles?.includes("admin") diff --git a/src/data/sidebar-items-paciente.json b/src/data/sidebar-items-paciente.json new file mode 100644 index 00000000..bb15caf7 --- /dev/null +++ b/src/data/sidebar-items-paciente.json @@ -0,0 +1,13 @@ +[ +{ + "name": "Minhas consulta", + "icon": "calendar-plus-fill", + "url": "/paciente/agendamento" + }, + + { + "name": "Meus laudos", + "icon": "table", + "url": "/paciente/laudo" + } +] diff --git a/src/perfis/Perfil_paciente/Perfilpaciente.jsx b/src/perfis/Perfil_paciente/Perfilpaciente.jsx new file mode 100644 index 00000000..d7a64d71 --- /dev/null +++ b/src/perfis/Perfil_paciente/Perfilpaciente.jsx @@ -0,0 +1,23 @@ +import { Routes, Route } from "react-router-dom"; +import Sidebar from "../../components/Sidebar"; +import PacienteItems from "../../data/sidebar-items-paciente.json"; +import Agendamento from "../../pages/Agendamento"; +import LaudoManager from "../../pages/LaudoManager"; +function PerfilPaciente({ onLogout }) { + return ( +
+ + +
+ + } /> + } /> + } /> + Página não encontrada} /> + +
+
+ ); +} + +export default PerfilPaciente; \ No newline at end of file From 13e4064989fcc704adcb4653ca307e3fc59d1598 Mon Sep 17 00:00:00 2001 From: jp-lima Date: Wed, 15 Oct 2025 16:47:52 -0300 Subject: [PATCH 08/14] Melhorias no estilo --- .../AgendarConsulta/CardConsulta.jsx | 89 ++++++-- .../AgendarConsulta/FormNovaConsulta.jsx | 8 +- .../AgendarConsulta/TabelaAgendamentoDia.jsx | 10 +- .../AgendarConsulta/TabelaAgendamentoMes.jsx | 20 +- .../TabelaAgendamentoSemana.jsx | 13 +- .../style/styleTabelas/tabeladia.css | 7 +- .../style/styleTabelas/tabelames.css | 8 +- src/pages/Agendamento.jsx | 214 +++++------------- src/pages/AgendamentoCadastroManager.jsx | 58 +++++ src/pages/AgendamentoEditPage.jsx | 70 ++++++ src/pages/style/Agendamento.css | 73 +++++- .../perfil_secretaria/PerfilSecretaria.jsx | 4 +- 12 files changed, 369 insertions(+), 205 deletions(-) create mode 100644 src/pages/AgendamentoCadastroManager.jsx create mode 100644 src/pages/AgendamentoEditPage.jsx diff --git a/src/components/AgendarConsulta/CardConsulta.jsx b/src/components/AgendarConsulta/CardConsulta.jsx index accb6ccc..acd627c7 100644 --- a/src/components/AgendarConsulta/CardConsulta.jsx +++ b/src/components/AgendarConsulta/CardConsulta.jsx @@ -1,39 +1,86 @@ import React, { useState, useEffect } from 'react';import { GetDoctorByID } from '../utils/Functions-Endpoints/Doctor'; import { GetPatientByID } from '../utils/Functions-Endpoints/Patient'; import { useAuth } from '../utils/AuthProvider'; -const CardConsulta = ( {DadosConsulta, TabelaAgendamento} ) => { +import { useNavigate } from 'react-router-dom'; +import { useMemo } from 'react'; + +const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal} ) => { + const navigate = useNavigate(); + const {getAuthorizationHeader} = useAuth() const authHeader = getAuthorizationHeader() const [Paciente, setPaciente] = useState() const [Medico, setMedico] = useState() - const BuscarMedicoEPaciente = async () => { - const Doctor = await GetDoctorByID(DadosConsulta.doctor_id, authHeader) - const Patient = await GetPatientByID(DadosConsulta.patient_id, authHeader) - setMedico(Doctor[0]) - setPaciente(Patient[0]) - console.log(Doctor, Patient) - } + const ids = useMemo(() => { + return { + doctor_id: DadosConsulta?.doctor_id, + patient_id: DadosConsulta?.patient_id, + status: DadosConsulta?.status + }; + }, [DadosConsulta]); - // Status (agendado, confirmado, realizado, cancelado) - useEffect(() => { - if(DadosConsulta.status !== 'nada'){BuscarMedicoEPaciente()} - -}, []) + + useEffect(() => { + const BuscarMedicoEPaciente = async () => { + if (!ids.doctor_id || !ids.patient_id || ids.status === 'nada') return; + + try { + const [Doctor, Patient] = await Promise.all([ + GetDoctorByID(ids.doctor_id, authHeader), + GetPatientByID(ids.patient_id, authHeader) + ]); + + setMedico(Doctor?.[0] || null); + setPaciente(Patient?.[0] || null); + } catch (error) { + console.error('Erro ao buscar médico/paciente:', error); + } + }; + + BuscarMedicoEPaciente(); + }, [ids, authHeader]); return ( -
console.log('Clicou n aconsulta')}> +
- {DadosConsulta.status !== 'vazio'? + {DadosConsulta.id? +
-
-

{DadosConsulta.horario} {Medico?.full_name}

-
-
-

{Paciente?.full_name} - {DadosConsulta.exam}

-
+
+
+

{DadosConsulta.horario} {Medico?.full_name}

+
+ +
+

{Paciente?.full_name} - {DadosConsulta.exam}

+
+
+ +
+ + + + + +
+
: null diff --git a/src/components/AgendarConsulta/FormNovaConsulta.jsx b/src/components/AgendarConsulta/FormNovaConsulta.jsx index d45e6596..bdae91d9 100644 --- a/src/components/AgendarConsulta/FormNovaConsulta.jsx +++ b/src/components/AgendarConsulta/FormNovaConsulta.jsx @@ -5,13 +5,14 @@ import { GetPatientByCPF } from "../utils/Functions-Endpoints/Patient"; import { GetDoctorByName } from "../utils/Functions-Endpoints/Doctor"; import { useAuth } from "../utils/AuthProvider"; -const FormNovaConsulta = ({ onCancel, onSave }) => { +const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) => { const {getAuthorizationHeader} = useAuth() const [selectedFile, setSelectedFile] = useState(null); const [anexos, setAnexos] = useState([]); const [loadingAnexos, setLoadingAnexos] = useState(false); - const [agendamento, setAgendamento] = useState({}) + + const [acessibilidade, setAcessibilidade] = useState({cadeirante:false,idoso:false,gravida:false,bebe:false, autista:false }) let authHeader = getAuthorizationHeader() @@ -40,9 +41,6 @@ const FormNovaConsulta = ({ onCancel, onSave }) => { const handleChange = (e) => { const {value, name} = e.target; - - console.log(value, name, agendamento) - if(name === 'email'){ setAgendamento({...agendamento, contato:{ ...agendamento.contato, diff --git a/src/components/AgendarConsulta/TabelaAgendamentoDia.jsx b/src/components/AgendarConsulta/TabelaAgendamentoDia.jsx index 15cca203..a248f234 100644 --- a/src/components/AgendarConsulta/TabelaAgendamentoDia.jsx +++ b/src/components/AgendarConsulta/TabelaAgendamentoDia.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import CardConsulta from './CardConsulta'; import "./style/styleTabelas/tabeladia.css"; -const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos }) => { +const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDeleteModal }) => { const [indiceAcesso, setIndiceAcesso] = useState(0) const [Dia, setDia] = useState() const agendamentosDoDia = agendamentos?.semana1?.segunda || []; @@ -23,10 +23,12 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos }) => { return (
- +
+

{Dia}

- +
+
@@ -41,7 +43,7 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos }) => { diff --git a/src/components/AgendarConsulta/TabelaAgendamentoMes.jsx b/src/components/AgendarConsulta/TabelaAgendamentoMes.jsx index 616c4013..26dcb313 100644 --- a/src/components/AgendarConsulta/TabelaAgendamentoMes.jsx +++ b/src/components/AgendarConsulta/TabelaAgendamentoMes.jsx @@ -174,17 +174,18 @@ const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos }) => { return (
-
- -

{AgendamentosMensaisOrganizados[indice].nomeDoMes}

- - +
+
+ +

{AgendamentosMensaisOrganizados[indice].nomeDoMes}

+ + +
-

{agendamento.horario}

handleClickAgendamento(agendamento)}> - +
- + @@ -196,16 +197,13 @@ const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos }) => { const semana = AgendamentosMensaisOrganizados[indice][semanaKey] console.log(AgendamentosMensaisOrganizados[indice][semanaKey], 'ajdsahchbaohdfoduh') - - - return( { semana && typeof semana === "object" && Object.keys(semana).map((dia) => ( - diff --git a/src/components/AgendarConsulta/style/styleTabelas/tabeladia.css b/src/components/AgendarConsulta/style/styleTabelas/tabeladia.css index 62284339..0e94023d 100644 --- a/src/components/AgendarConsulta/style/styleTabelas/tabeladia.css +++ b/src/components/AgendarConsulta/style/styleTabelas/tabeladia.css @@ -114,4 +114,9 @@ html[data-bs-theme="dark"] .mostrar-horario th { border: 1px solid #333; color: #e0e0e0; background-color: #232323; -} \ No newline at end of file +} +/* +.container-botons{ + margin-left: 10rem; + background-color: pink; +}*/ \ No newline at end of file diff --git a/src/components/AgendarConsulta/style/styleTabelas/tabelames.css b/src/components/AgendarConsulta/style/styleTabelas/tabelames.css index b63f5e14..21972607 100644 --- a/src/components/AgendarConsulta/style/styleTabelas/tabelames.css +++ b/src/components/AgendarConsulta/style/styleTabelas/tabelames.css @@ -221,6 +221,12 @@ html[data-bs-theme="dark"] .cards-que-faltam { color: #90caf9; } -th{ +.cabecalho-tabela{ color: white; + background-color: #005a9e; +} + +.container-botons{ + margin-left: 5rem; + } \ No newline at end of file diff --git a/src/pages/Agendamento.jsx b/src/pages/Agendamento.jsx index cc0bebf7..5266b66a 100644 --- a/src/pages/Agendamento.jsx +++ b/src/pages/Agendamento.jsx @@ -1,6 +1,6 @@ import React, { useState, useMemo, useEffect } from 'react'; import API_KEY from '../components/utils/apiKeys.js'; - +import AgendamentoCadastroManager from './AgendamentoCadastroManager.jsx'; import TabelaAgendamentoDia from '../components/AgendarConsulta/TabelaAgendamentoDia'; import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana'; import TabelaAgendamentoMes from '../components/AgendarConsulta/TabelaAgendamentoMes'; @@ -16,115 +16,6 @@ import './style/FilaEspera.css'; const Agendamento = () => { - // Dados mocados para simular consultas com a API de Hugo - const Agendamentos = [ - { - id: "", - order_number: "APT-2025-0001", - patient_id: "a8039e6d-7271-4187-a719-e27d9c6d15b3", - appointment_type: "presencial", - cancellation_reason: null, - cancelled_at: null, - checked_in_at: null, - chief_complaint: "Dor de cabeça há 3", - completed_at: null, - created_at: "2025-10-10T15:56:59.112231+00:00", - created_by: "87f2662c-9da7-45c0-9e05-521d9d92d105", - doctor_id: "c2715ddb-e8fb-4319-8015-4fd5df93a39b", - duration_minutes: 30, - insurance_provider: "Unimed", - notes: null, - patient_notes: "Prefiro horário pela manhã", - scheduled_at: "2025-10-10T15:56:58.937+00:00", - status: "requested", - updated_at: "2025-10-10T15:56:59.112231+00:00", - updated_by: null - }, - // mesma data - { - id: "", - order_number: "APT-2025-0002", - patient_id: "becd4c18-042e-44ad-9bcb-cfef08f18046", - appointment_type: "presencial", - chief_complaint: "Retorno de consulta", - created_at: "2025-10-10T09:30:00.000+00:00", - scheduled_at: "2025-10-10T09:30:00.000+00:00", - doctor_id: "c2715ddb-e8fb-4319-8015-4fd5df93a39b", - duration_minutes: 45, - insurance_provider: "Amil", - status: "requested" - }, - // dia anterior 1 - { - id: "", - order_number: "APT-2025-0003", - patient_id: "e17e2bc6-6a90-4dc6-ae2d-b503e2835d36", - appointment_type: "teleconsulta", - chief_complaint: "Tosse persistente", - created_at: "2025-10-09T10:15:00.000+00:00", - scheduled_at: "2025-10-09T10:15:00.000+00:00", - doctor_id: "c2715ddb-e8fb-4319-8015-4fd5df93a39b", - duration_minutes: 20, - insurance_provider: "Bradesco Saúde", - status: "confirmed" - }, - // dia anterior 2 - { - id: "", - order_number: "APT-2025-0004", - patient_id: "d20e418f-6e45-495a-98be-16a9a163fab3", - appointment_type: "presencial", - chief_complaint: "Check-up anual", - created_at: "2025-10-09T14:00:00.000+00:00", - scheduled_at: "2025-10-09T14:00:00.000+00:00", - doctor_id: "c2715ddb-e8fb-4319-8015-4fd5df93a39b", - duration_minutes: 60, - insurance_provider: "Unimed", - status: "requested" - }, - // dia seguinte - { - id: "", - order_number: "APT-2025-0005", - patient_id: "8f27e87d-851a-484a-8450-6e3a5f29476c", - appointment_type: "presencial", - chief_complaint: "Dor lombar", - created_at: "2025-10-11T11:45:00.000+00:00", - scheduled_at: "2025-10-08T11:45:00.000+00:00", - doctor_id: "9471fb52-4de6-4052-b173-1f0695173ba3", - duration_minutes: 30, - insurance_provider: "SulAmérica", - status: "requested" - }, - // outro qualquer (para variedade) - { - id: "", - order_number: "APT-2025-0006", - patient_id: "47902ada-7d04-480a-a759-bae8a211973a", - appointment_type: "teleconsulta", - chief_complaint: "Acompanhamento pós-cirurgia", - created_at: "2025-10-10T18:00:00.000+00:00", - scheduled_at: "2025-10-10T18:00:00.000+00:00", - doctor_id: "9471fb52-4de6-4052-b173-1f0695173ba3", - duration_minutes: 25, - insurance_provider: "Unimed", - status: "requested" - }, { id: "", - order_number: "APT-2025-0006", - patient_id: "47902ada-7d04-480a-a759-bae8a211973a", - appointment_type: "teleconsulta", - chief_complaint: "Acompanhamento pós-cirurgia", - created_at: "2025-10-10T18:00:00.000+00:00", - scheduled_at: "2025-10-24T18:00:00.000+00:00", - doctor_id: "9471fb52-4de6-4052-b173-1f0695173ba3", - duration_minutes: 25, - insurance_provider: "Unimed", - status: "requested"} - -]; - - - const [FiladeEspera, setFiladeEspera] = useState(false); const [tabela, setTabela] = useState('diario'); const [PageNovaConsulta, setPageConsulta] = useState(false); @@ -133,19 +24,13 @@ const Agendamento = () => { const {getAuthorizationHeader} = useAuth() const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({}) + const [showDeleteModal, setShowDeleteModal] = useState(false) const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState() let authHeader = getAuthorizationHeader() - - // id do doutor - // dias - const FiltrarAgendamentos = (listaTodosAgendamentos) => { - - - // cria um dicionário temporário let DictAgendamentosOrganizados = {}; for (let i = 0; i < listaTodosAgendamentos.length; i++) { @@ -168,38 +53,6 @@ const Agendamento = () => { } - const handleSave = (Dict) => { - let DataAtual = dayjs() - var myHeaders = new Headers(); - myHeaders.append("apikey", API_KEY); - myHeaders.append("Authorization", authHeader); - myHeaders.append("Content-Type", "application/json"); - -var raw = JSON.stringify({ - "patient_id": Dict.patient_id, - "doctor_id": Dict.doctor_id, - "scheduled_at": DataAtual, - "duration_minutes": 30, - "appointment_type": "presencial", - "chief_complaint": "Dor de cabeça há 3 ", - "patient_notes": "Prefiro horário pela manhã", - "insurance_provider": "Unimed", - "created_by": "87f2662c-9da7-45c0-9e05-521d9d92d105" -}); - -var requestOptions = { - method: 'POST', - headers: myHeaders, - body: raw, - redirect: 'follow' -}; - -fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOptions) - .then(response => response.text()) - .then(result => console.log(result)) - .catch(error => console.log('error', error)); - - } // Requisição inicial para mostrar os agendamentos do banco de dados useEffect(() => { @@ -215,7 +68,7 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOp fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions) .then(response => response.json()) - .then(result => {FiltrarAgendamentos(Agendamentos); console.log(Agendamentos, "aqui")}) + .then(result => {FiltrarAgendamentos(result); console.log(result, "aqui")}) .catch(error => console.log('error', error)); }, []) @@ -371,7 +224,7 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOp - {tabela === "diario" && } + {tabela === "diario" && } {tabela === 'semanal' && } {tabela === 'mensal' && } @@ -418,8 +271,65 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOp ) : ( - + )} + + {showDeleteModal && ( +
+ e.target.classList.contains("modal") && setShowDeleteModal(false) + } + > +
+
+ +
+
+ Confirmação de Exclusão +
+ +
+ +
+

+ Tem certeza que deseja excluir este paciente? +

+
+ +
+ + + + + +
+
+
+
)} + + ) } diff --git a/src/pages/AgendamentoCadastroManager.jsx b/src/pages/AgendamentoCadastroManager.jsx new file mode 100644 index 00000000..7c976402 --- /dev/null +++ b/src/pages/AgendamentoCadastroManager.jsx @@ -0,0 +1,58 @@ +import React from 'react' +import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta' +import API_KEY from '../components/utils/apiKeys' +import { useAuth } from '../components/utils/AuthProvider' +import { useState } from 'react' +import dayjs from 'dayjs' + +const AgendamentoCadastroManager = () => { + + const {getAuthorizationHeader} = useAuth() + const [agendamento, setAgendamento] = useState({}) + + + let authHeader = getAuthorizationHeader() + + const handleSave = (Dict) => { + let DataAtual = dayjs() + var myHeaders = new Headers(); + myHeaders.append("apikey", API_KEY); + myHeaders.append("Authorization", authHeader); + myHeaders.append("Content-Type", "application/json"); + + var raw = JSON.stringify({ + "patient_id": Dict.patient_id, + "doctor_id": Dict.doctor_id, + "scheduled_at": DataAtual, + "duration_minutes": 30, + "appointment_type": "presencial", + "chief_complaint": "Dor de cabeça há 3 ", + "patient_notes": "Prefiro horário pela manhã", + "insurance_provider": "Unimed", + "created_by": "87f2662c-9da7-45c0-9e05-521d9d92d105" + }); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' + }; + + fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOptions) + .then(response => response.text()) + .then(result => console.log(result)) + .catch(error => console.log('error', error)); + + } + + return ( +
+ + + +
+ ) +} + +export default AgendamentoCadastroManager \ No newline at end of file diff --git a/src/pages/AgendamentoEditPage.jsx b/src/pages/AgendamentoEditPage.jsx new file mode 100644 index 00000000..46f37111 --- /dev/null +++ b/src/pages/AgendamentoEditPage.jsx @@ -0,0 +1,70 @@ +import React from 'react' +import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta' +import { useState } from 'react' +import { useParams } from 'react-router-dom' +import API_KEY from '../components/utils/apiKeys' +import { useAuth } from '../components/utils/AuthProvider' +import dayjs from 'dayjs' + + +const AgendamentoEditPage = () => { + + let DataAtual = dayjs() + const {getAuthorizationHeader} = useAuth() + const params = useParams() + const [PatientToPatch, setPatientToPatch] = useState({}) + + let id = params.id + + console.log(id) + + let authHeader = getAuthorizationHeader() + + const handleSave = (DictParaPatch) => { + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append('apikey', API_KEY) + myHeaders.append("authorization", authHeader) + + console.log(DictParaPatch) + + var raw = JSON.stringify({"patient_id": DictParaPatch.patient_id, + "doctor_id": DictParaPatch.doctor_id, + "scheduled_at": DataAtual, + "duration_minutes": 30, + "appointment_type": "presencial", + "chief_complaint": "Dor de cabeça há 3 ", + "patient_notes": "Prefiro horário pela manhã", + "insurance_provider": "Unimed", + "created_by": "87f2662c-9da7-45c0-9e05-521d9d92d105" + + + }); + + console.log(DictParaPatch) + console.log(id) + + var requestOptions = { + method: 'PATCH', + headers: myHeaders, + body: raw, + redirect: 'follow' + }; + + fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${id}`, requestOptions) + .then(response => response.text()) + .then(result => console.log(result)) + .catch(error => console.log('error', error)); + } + + + return ( +
+ + + +
+ ) +} + +export default AgendamentoEditPage diff --git a/src/pages/style/Agendamento.css b/src/pages/style/Agendamento.css index 8df2e113..8d1e7eb4 100644 --- a/src/pages/style/Agendamento.css +++ b/src/pages/style/Agendamento.css @@ -118,7 +118,7 @@ border-radius: 10px; } -#status-card-consulta-confirmado, .legenda-item-confirmado { +#status-card-consulta-confirmado, .legenda-item-confirmed { background-color: #eef8fb; border:3px solid #d8dfe7; padding: 5px; @@ -288,4 +288,73 @@ html[data-bs-theme="dark"] { color: #fff; background-color: #005a9e; } -} \ No newline at end of file +} + +/* Estilo para o botão de Editar */ +.btn-edit-custom { + background-color: #FFF3CD; + color: #856404; +} + +/* Estilo para o botão de Excluir (Deletar) */ +.btn-delete-custom { + background-color: #F8D7DA; + color: #721C24; + padding: 10px; +} + +.cardconsulta{ + display:flex; + align-items: center; + flex-direction: row; +} + +.container-botons{ + display: flex; + flex-direction: row; +} + +#tabela-seletor-container { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + + background-color: #fff; + border-radius: 8px; + padding: 6px 12px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); + + font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto; + width: fit-content; + margin: 0 auto; +} + +#tabela-seletor-container p { + margin: 0; + font-size: 23px; + font-weight: 500; + color: #4085f6; + text-align: center; + white-space: nowrap; +} + +#tabela-seletor-container button { + background: transparent; + border: none; + color: #555; + font-size: 20px; + cursor: pointer; + padding: 4px 6px; + border-radius: 6px; + transition: all 0.2s ease-in-out; +} + +#tabela-seletor-container button:hover { + background-color: rgba(0, 0, 0, 0.05); + color: #000; +} + +#tabela-seletor-container i { + pointer-events: none; +} diff --git a/src/perfis/perfil_secretaria/PerfilSecretaria.jsx b/src/perfis/perfil_secretaria/PerfilSecretaria.jsx index baa72a69..b59d2d22 100644 --- a/src/perfis/perfil_secretaria/PerfilSecretaria.jsx +++ b/src/perfis/perfil_secretaria/PerfilSecretaria.jsx @@ -15,6 +15,7 @@ import Details from "../../pages/Details"; import EditPage from "../../pages/EditPage"; import DoctorDetails from "../../pages/DoctorDetails"; import DoctorEditPage from "../../pages/DoctorEditPage"; +import AgendamentoEditPage from "../../pages/AgendamentoEditPage"; function PerfilSecretaria({ onLogout }) { return ( @@ -33,7 +34,8 @@ function PerfilSecretaria({ onLogout }) { } /> } /> } /> - } /> + } /> + } /> Página não encontrada} /> From 350e63d692c975576968e72b3b3200f29e335d86 Mon Sep 17 00:00:00 2001 From: Eduarda-SS <137419071+Eduarda-SS@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:50:35 -0300 Subject: [PATCH 09/14] Excessoes: tentativa 1 --- .../AgendarConsulta/FormNovaConsulta.jsx | 234 ++++++++++++++---- 1 file changed, 179 insertions(+), 55 deletions(-) diff --git a/src/components/AgendarConsulta/FormNovaConsulta.jsx b/src/components/AgendarConsulta/FormNovaConsulta.jsx index 435bc8ee..68f72eac 100644 --- a/src/components/AgendarConsulta/FormNovaConsulta.jsx +++ b/src/components/AgendarConsulta/FormNovaConsulta.jsx @@ -5,11 +5,22 @@ import { useState, useEffect } from "react"; const FormNovaConsulta = ({ onCancel, patientID }) => { + const [isModoEmergencia, setIsModoEmergencia] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [anexos, setAnexos] = useState([]); const [loadingAnexos, setLoadingAnexos] = useState(false); const [paciente, setPaciente] = useState({}) const [acessibilidade, setAcessibilidade] = useState({cadeirante:false,idoso:false,gravida:false,bebe:false, autista:false }) + const [dadosAtendimento, setDadosAtendimento] = useState({ + profissional: '', + tipoAtendimento: '', + unidade: '', + dataAtendimento: '', + inicio: '', + termino: '', + solicitante: '', + observacoes: '' + }); useEffect(() => { if (!patientID) return; @@ -165,9 +176,73 @@ const FormNovaConsulta = ({ onCancel, patientID }) => { } } + const handleAtendimentoChange = (e) => { + const { value, name } = e.target; + setDadosAtendimento(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmitExcecao = async () => { + console.log("Modo Emergência Ativado: Tentando criar Exceção com novo endpoint."); + + const { profissional, dataAtendimento, tipoAtendimento, inicio, termino, observacoes } = dadosAtendimento; + + if (!profissional || !dataAtendimento || !tipoAtendimento || !inicio || !termino) { + alert("Por favor, preencha o Profissional, Data, Tipo e Horários para a exceção."); + return; + } + + const payload = { + doctor_id: profissional, + date: dataAtendimento, + start_time: inicio + ":00", // Adiciona ":00" se o input type="time" retornar apenas HH:MM + end_time: termino + ":00", // Adiciona ":00" + kind: "liberacao", // Usando 'excecao' ou 'consulta' ao invés de 'bloqueio' + reason: tipoAtendimento, + //observation: observacoes || "Agendamento fora da grade horária padrão.", + }; + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: JSON.stringify(payload), + redirect: 'follow' + }; + + try { + const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_exceptions", requestOptions); + const result = await response.json(); + + if (response.ok || response.status === 201) { + console.log("Exceção de emergência criada com sucesso:", result); + alert(`Consulta de emergência agendada como exceção! Detalhes: ${JSON.stringify(result)}`); + } else { + console.error("Erro ao criar exceção de emergência:", result); + alert(`Erro ao agendar exceção. Status: ${response.status}. Detalhes: ${result.message || JSON.stringify(result)}`); + } + } catch (error) { + console.error("Erro na requisição para criar exceção:", error); + alert("Erro de comunicação com o servidor ou formato de resposta inválido."); + } + }; + + const handleSubmitPadrao = () => { + console.log("Salvando agendamento."); + alert("Agendamento salvo!"); + }; + const handleSubmit = (e) => { e.preventDefault(); - alert("Agendamento salvo!"); + if (isModoEmergencia) { + handleSubmitExcecao(); + } else { + handleSubmitPadrao(); + } }; return ( @@ -259,10 +334,21 @@ const FormNovaConsulta = ({ onCancel, patientID }) => { )}

Informações do atendimento

- - +
+ + {isModoEmergencia && ( +

⚠️ As informações de data e horário serão enviadas como uma exceção fora da grade normal.

+ )} +
+
-
handleclickAcessibilidade(e.currentTarget.id)}> accessible @@ -287,72 +373,110 @@ const FormNovaConsulta = ({ onCancel, patientID }) => {
-
- +
- + +
+
+ +
- - -
- -
-
-
-
- - +
+
+ + +
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
- - -
- - +
+ + +
-
+
-
-
- - -
+
- - + +
- -
- - -
-
-
- -
- - -
- - -
-
- + +
From a6755aeb37ecaa2afd029b072211fc995af7856b Mon Sep 17 00:00:00 2001 From: Eduarda-SS <137419071+Eduarda-SS@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:21:17 -0300 Subject: [PATCH 10/14] =?UTF-8?q?Vers=C3=A3o=20final=20das=20excecoes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AgendarConsulta/FormDisponibilidade.jsx | 177 ++++++++++++++++++ src/pages/Agendamento.jsx | 6 + .../perfil_secretaria/PerfilSecretaria.jsx | 2 + 3 files changed, 185 insertions(+) create mode 100644 src/components/AgendarConsulta/FormDisponibilidade.jsx diff --git a/src/components/AgendarConsulta/FormDisponibilidade.jsx b/src/components/AgendarConsulta/FormDisponibilidade.jsx new file mode 100644 index 00000000..86ba6cec --- /dev/null +++ b/src/components/AgendarConsulta/FormDisponibilidade.jsx @@ -0,0 +1,177 @@ +import InputMask from "react-input-mask"; +import "./style/formagendamentos.css"; +import { useState, useEffect } from "react"; + +const FormNovaDisponibilidade = ({ onCancel, doctorID }) => { + + const [dadosAtendimento, setDadosAtendimento] = useState({ + profissional: '', + tipoAtendimento: '', + dataAtendimento: '', + inicio: '', + termino: '', + motivo: '' + }); + + const handleAtendimentoChange = (e) => { + const { value, name } = e.target; + setDadosAtendimento(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmitExcecao = async (e) => { + e.preventDefault(); + console.log("Modo Emergência Ativado: Tentando criar Exceção com novo endpoint."); + + const { profissional, dataAtendimento, tipoAtendimento, inicio, termino, motivo } = dadosAtendimento; + + if (!profissional || !dataAtendimento || !tipoAtendimento) { + alert("Por favor, preencha o Profissional, Data, e Tipo da exceção."); + return; + } + + const payload = { + doctor_id: profissional, + date: dataAtendimento, + start_time: inicio + ":00" || null, // Adiciona ":00" se o input type="time" retornar apenas HH:MM + end_time: termino + ":00" || null, // Adiciona ":00" + kind: tipoAtendimento, + reason: motivo, + }; + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: JSON.stringify(payload), + redirect: 'follow' + }; + + try { + const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_exceptions", requestOptions); + const result = await response.json(); + + if (response.ok || response.status === 201) { + console.log("Exceção de emergência criada com sucesso:", result); + alert(`Consulta de emergência agendada como exceção! Detalhes: ${JSON.stringify(result)}`); + } else { + console.error("Erro ao criar exceção de emergência:", result); + alert(`Erro ao agendar exceção. Status: ${response.status}. Detalhes: ${result.message || JSON.stringify(result)}`); + } + } catch (error) { + console.error("Erro na requisição para criar exceção:", error); + alert("Erro de comunicação com o servidor ou formato de resposta inválido."); + } + }; + + return ( +
+
+

Informações do médico

+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+ +
+ + +
+
+
+ +
+ + +
+ +
+ ); +}; + +export default FormNovaDisponibilidade; \ No newline at end of file diff --git a/src/pages/Agendamento.jsx b/src/pages/Agendamento.jsx index d693cfee..cc856562 100644 --- a/src/pages/Agendamento.jsx +++ b/src/pages/Agendamento.jsx @@ -1,4 +1,5 @@ import React, { useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import TabelaAgendamentoDia from '../components/AgendarConsulta/TabelaAgendamentoDia'; import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana'; @@ -13,6 +14,7 @@ import "./style/Agendamento.css"; import './style/FilaEspera.css'; const Agendamento = () => { + const navigate = useNavigate(); const [FiladeEspera, setFiladeEspera] = useState(false); const [tabela, setTabela] = useState('diario'); @@ -91,6 +93,10 @@ const Agendamento = () => {

Agendar nova consulta

+ + {!PageNovaConsulta ? (
diff --git a/src/perfis/perfil_secretaria/PerfilSecretaria.jsx b/src/perfis/perfil_secretaria/PerfilSecretaria.jsx index baa72a69..0ff435a2 100644 --- a/src/perfis/perfil_secretaria/PerfilSecretaria.jsx +++ b/src/perfis/perfil_secretaria/PerfilSecretaria.jsx @@ -15,6 +15,7 @@ import Details from "../../pages/Details"; import EditPage from "../../pages/EditPage"; import DoctorDetails from "../../pages/DoctorDetails"; import DoctorEditPage from "../../pages/DoctorEditPage"; +import FormDisponibilidade from "../../components/AgendarConsulta/FormDisponibilidade"; function PerfilSecretaria({ onLogout }) { return ( @@ -35,6 +36,7 @@ function PerfilSecretaria({ onLogout }) { } /> } /> Página não encontrada} /> + } />
From d341d0142a6206c6a79d350b504be978b6e82a2f Mon Sep 17 00:00:00 2001 From: pedrofedericoo Date: Wed, 15 Oct 2025 19:49:32 -0300 Subject: [PATCH 11/14] disponibilidade no agendamento --- .../AgendarConsulta/FormNovaConsulta.jsx | 713 +++++++++++++++--- 1 file changed, 611 insertions(+), 102 deletions(-) diff --git a/src/components/AgendarConsulta/FormNovaConsulta.jsx b/src/components/AgendarConsulta/FormNovaConsulta.jsx index 1e7fb54f..a17c2211 100644 --- a/src/components/AgendarConsulta/FormNovaConsulta.jsx +++ b/src/components/AgendarConsulta/FormNovaConsulta.jsx @@ -2,7 +2,11 @@ import InputMask from "react-input-mask"; import "./style/formagendamentos.css"; import { useState, useEffect } from "react"; -const FormNovaConsulta = ({ onCancel, patientID, profissionalId }) => { +const FormNovaConsulta = ({ onCancel, patientID }) => { + const [horariosDisponiveis, setHorariosDisponiveis] = useState([]); + const [carregandoHorarios, setCarregandoHorarios] = useState(false); + + const [isModoEmergencia, setIsModoEmergencia] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [anexos, setAnexos] = useState([]); const [loadingAnexos, setLoadingAnexos] = useState(false); @@ -14,11 +18,68 @@ const FormNovaConsulta = ({ onCancel, patientID, profissionalId }) => { bebe: false, autista: false, }); + const [dadosAtendimento, setDadosAtendimento] = useState({ + profissional: "", + tipoAtendimento: "", + unidade: "", + dataAtendimento: "", + inicio: "", + termino: "", + solicitante: "", + observacoes: "", + }); - const [disponibilidades, setDisponibilidades] = useState([]); - const [horarioSelecionado, setHorarioSelecionado] = useState(""); + // Variável de controle para saber se a grade de horário deve ser mostrada + const isReadyForSchedule = + dadosAtendimento.profissional && dadosAtendimento.dataAtendimento; + + const fetchHorariosDisponiveis = async (professionalId, date) => { + if (!isReadyForSchedule || isModoEmergencia) { + setHorariosDisponiveis([]); + return; + } + + setCarregandoHorarios(true); + setHorariosDisponiveis([]); + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + + const payload = { + doctor_id: professionalId, + date: date, + }; + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: JSON.stringify(payload), + redirect: "follow", + }; + + try { + const res = await fetch( + "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability", + requestOptions + ); + const data = await res.json(); + + const slots = data.data && Array.isArray(data.data) ? data.data : []; + + setHorariosDisponiveis(slots); + + // Limpa o horário se o que estava selecionado não existe mais na nova grade + if (dadosAtendimento.inicio && !slots.includes(dadosAtendimento.inicio)) { + setDadosAtendimento((prev) => ({ ...prev, inicio: "", termino: "" })); + } + } catch (err) { + console.error("Erro ao buscar horários disponíveis:", err); + setHorariosDisponiveis([]); + } finally { + setCarregandoHorarios(false); + } + }; - // Buscar anexos do paciente useEffect(() => { if (!patientID) return; @@ -40,48 +101,22 @@ const FormNovaConsulta = ({ onCancel, patientID, profissionalId }) => { fetchAnexos(); }, [patientID]); - // Buscar disponibilidades do médico useEffect(() => { - if (!profissionalId) return; - - const fetchDisponibilidades = async () => { - try { - const res = await fetch( - `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability?doctorId=${profissionalId}` - ); - const data = await res.json(); - setDisponibilidades(data.data || []); - } catch (err) { - console.error("Erro ao buscar disponibilidades:", err); - } - }; - - fetchDisponibilidades(); - }, [profissionalId]); - - // Função para criar intervalos de 30 minutos - const gerarIntervalos = (inicio, fim) => { - const horarios = []; - let horaAtual = new Date(`1970-01-01T${inicio}:00`); - const horaFim = new Date(`1970-01-01T${fim}:00`); - - while (horaAtual < horaFim) { - const proximo = new Date(horaAtual.getTime() + 30 * 60000); - if (proximo <= horaFim) { - horarios.push( - `${horaAtual.toTimeString().slice(0, 5)} - ${proximo - .toTimeString() - .slice(0, 5)}` - ); - } - horaAtual = proximo; + // Chama a busca apenas se estivermos no modo padrão E tivermos profissional e data + if (isReadyForSchedule && !isModoEmergencia) { + fetchHorariosDisponiveis( + dadosAtendimento.profissional, + dadosAtendimento.dataAtendimento + ); + } else if (!isReadyForSchedule) { + setHorariosDisponiveis([]); } - return horarios; - }; - - const intervalosDisponiveis = disponibilidades.flatMap((disp) => - gerarIntervalos(disp.horarioInicial, disp.horarioFinal) - ); + }, [ + dadosAtendimento.profissional, + dadosAtendimento.dataAtendimento, + isModoEmergencia, + isReadyForSchedule, + ]); const handleUpload = async () => { if (!selectedFile) return; @@ -110,14 +145,20 @@ const FormNovaConsulta = ({ onCancel, patientID, profissionalId }) => { }; const handleclickAcessibilidade = (id) => { - setAcessibilidade({ - ...acessibilidade, - [id]: !acessibilidade[id], - }); + let resultado = acessibilidade[id]; + + if (resultado === false) { + setAcessibilidade({ ...acessibilidade, [id]: true }); + console.log("mudou"); + } else if (resultado === true) { + setAcessibilidade({ ...acessibilidade, [id]: false }); + } + console.log(id); }; const FormatCPF = (valor) => { const digits = String(valor).replace(/\D/g, "").slice(0, 11); + BuscarPacienteExistentePeloCPF(valor); return digits .replace(/(\d{3})(\d)/, "$1.$2") @@ -134,6 +175,58 @@ const FormNovaConsulta = ({ onCancel, patientID, profissionalId }) => { .replace(/(\d{4})(\d{4})/, "$1-$2"); }; + const BuscarCPFnoBancodeDados = async (cpf) => { + var myHeaders = new Headers(); + myHeaders.append("Authorization", "Bearer "); + myHeaders.append("Content-Type", "application/json"); + + var raw = JSON.stringify({ + cpf: cpf, + }); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + redirect: "follow", + }; + + const response = await fetch( + "https://mock.apidog.com/m1/1053378-0-default/pacientes/validar-cpf", + requestOptions + ); + const result = await response.json(); + return result; + }; + + const BuscarPacienteExistentePeloCPF = async (value) => { + if (isNaN(value[13]) === false && value.length === 14) + try { + const result = await BuscarCPFnoBancodeDados(value); + + if (result.data.existe === true) { + var myHeaders = new Headers(); + myHeaders.append("Authorization", "Bearer "); + + var requestOptions = { + method: "GET", + headers: myHeaders, + redirect: "follow", + }; + + fetch( + "https://mock.apidog.com/m1/1053378-0-default/pacientes/", + requestOptions + ) + .then((response) => response.json()) + .then((result) => setPaciente(result.data)) + .catch((error) => console.log("error", error)); + } + } catch (error) { + console.log("error", error); + } + }; + const handleChange = (e) => { const { value, name } = e.target; @@ -158,13 +251,118 @@ const FormNovaConsulta = ({ onCancel, patientID, profissionalId }) => { } }; + const handleAtendimentoChange = (e) => { + const { value, name } = e.target; + setDadosAtendimento((prev) => ({ + ...prev, + [name]: value, + })); + }; + + const handleSubmitExcecao = async () => { + console.log( + "Modo Emergência Ativado: Tentando criar Exceção com novo endpoint." + ); + + const { + profissional, + dataAtendimento, + tipoAtendimento, + inicio, + termino, + observacoes, + } = dadosAtendimento; + + if ( + !profissional || + !dataAtendimento || + !tipoAtendimento || + !inicio || + !termino + ) { + alert( + "Por favor, preencha o Profissional, Data, Tipo e Horários para a exceção." + ); + return; + } + + const payload = { + doctor_id: profissional, + date: dataAtendimento, + start_time: inicio + ":00", + end_time: termino + ":00", + kind: "liberacao", + reason: tipoAtendimento, + }; + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: JSON.stringify(payload), + redirect: "follow", + }; + + try { + const response = await fetch( + "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_exceptions", + requestOptions + ); + const result = await response.json(); + + if (response.ok || response.status === 201) { + console.log("Exceção de emergência criada com sucesso:", result); + alert( + `Consulta de emergência agendada como exceção! Detalhes: ${JSON.stringify( + result + )}` + ); + } else { + console.error("Erro ao criar exceção de emergência:", result); + alert( + `Erro ao agendar exceção. Status: ${response.status}. Detalhes: ${ + result.message || JSON.stringify(result) + }` + ); + } + } catch (error) { + console.error("Erro na requisição para criar exceção:", error); + alert( + "Erro de comunicação com o servidor ou formato de resposta inválido." + ); + } + }; + + const handleSubmitPadrao = () => { + if (!isReadyForSchedule) { + alert( + "Por favor, preencha o Profissional e a Data do Atendimento antes de salvar." + ); + return; + } + if ( + !horariosDisponiveis.includes(dadosAtendimento.inicio) || + !horariosDisponiveis.includes(dadosAtendimento.termino) + ) { + alert( + "Por favor, selecione horários válidos dentro da grade do profissional." + ); + return; + } + + console.log("Salvando agendamento."); + alert("Agendamento salvo!"); + }; + const handleSubmit = (e) => { e.preventDefault(); - alert( - `Agendamento salvo! Horário selecionado: ${ - horarioSelecionado || "não selecionado" - }` - ); + if (isModoEmergencia) { + handleSubmitExcecao(); + } else { + handleSubmitPadrao(); + } }; return ( @@ -172,14 +370,16 @@ const FormNovaConsulta = ({ onCancel, patientID, profissionalId }) => {

Informações do paciente

- {/* Campos do paciente */} -
+
{
+ { onChange={(e) => (e.target.value = FormatCPF(e.target.value))} />
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+
+ + +
+
+ + +
+ +
+ + +
- {/* Informações adicionais / anexos */}

Informações adicionais

+ @@ -228,63 +499,301 @@ const FormNovaConsulta = ({ onCancel, patientID, profissionalId }) => { )) )}
- - {/* Informações do atendimento */}

Informações do atendimento

+
+ + {isModoEmergencia && ( +

+ ⚠️ As informações de data e horário serão enviadas como uma + exceção fora da grade normal. +

+ )} +
+ +
+
handleclickAcessibilidade(e.currentTarget.id)} + > + accessible +
+ +
handleclickAcessibilidade(e.currentTarget.id)} + > + elderly +
+ +
handleclickAcessibilidade(e.currentTarget.id)} + > + + pregnant_woman + +
+ +
handleclickAcessibilidade(e.currentTarget.id)} + > + + + + + + +
+ +
handleclickAcessibilidade(e.currentTarget.id)} + > + + + +
+
- + {/* INPUT: Nome do profissional (usado para habilitar a busca de horários) */} +
-
- -
-
- - {/* Seção de datas e horários */} -
-
- - -
- -
- - -
- -
- - - - {intervalosDisponiveis.map((horario, i) => ( - - ))} - + value={dadosAtendimento.tipoAtendimento} + onChange={handleAtendimentoChange} + />
-
- - -
+
+
+
+
+ + +
+ +
+ + {/* INPUT: Data de Atendimento (usada para habilitar a busca de horários) */} + +
+
+ +
+ {isModoEmergencia ? ( + // MODO EMERGÊNCIA: Input type="time" simples, sem restrição + <> +
+ + +
+
+ + +
+ + ) : // MODO PADRÃO + isReadyForSchedule ? ( + // ESTADO 2: Médico e Data ESCOLHIDOS -> Restringe para grade (SELECT) + <> +
+ + {carregandoHorarios ? ( + + ) : ( + + )} +
+ +
+ + +
+ + ) : ( + // ESTADO 1: Médico ou Data PENDENTE -> Permite entrada de tempo livre (INPUT TYPE="TIME") + <> +
+ + +
+
+ + +
+ + )} + +
+ + +
+
+
+ +
+
+ + +
+
+
- )} + + + setSelectedFile(e.target.files[0])} + /> + {selectedFile && ( + + )}
{loadingAnexos ? (

Carregando anexos...

@@ -500,312 +259,109 @@ const FormNovaConsulta = ({ onCancel, patientID }) => { )}

Informações do atendimento

-
- - {isModoEmergencia && ( -

- ⚠️ As informações de data e horário serão enviadas como uma - exceção fora da grade normal. -

- )} -
+ + +
+ +
handleclickAcessibilidade(e.currentTarget.id)}> + + accessible +
-
-
handleclickAcessibilidade(e.currentTarget.id)} - > - accessible -
+
handleclickAcessibilidade(e.currentTarget.id)}> + elderly +
+ +
handleclickAcessibilidade(e.currentTarget.id)}> + pregnant_woman +
+ +
handleclickAcessibilidade(e.currentTarget.id)}> + -
handleclickAcessibilidade(e.currentTarget.id)} - > - elderly -
+
-
handleclickAcessibilidade(e.currentTarget.id)} - > - - pregnant_woman - +
handleclickAcessibilidade(e.currentTarget.id)}> + +
+
-
handleclickAcessibilidade(e.currentTarget.id)} - > - - - - - - -
- -
handleclickAcessibilidade(e.currentTarget.id)} - > - - - -
-
-
-
- - {/* INPUT: Nome do profissional (usado para habilitar a busca de horários) */} - -
+ +
+ + +
+ +
- +
-
-
- - -
- -
- - {/* INPUT: Data de Atendimento (usada para habilitar a busca de horários) */} - -
+ +
+
+ +
+ -
- {isModoEmergencia ? ( - // MODO EMERGÊNCIA: Input type="time" simples, sem restrição - <> -
- - -
-
- - -
- - ) : // MODO PADRÃO - isReadyForSchedule ? ( - // ESTADO 2: Médico e Data ESCOLHIDOS -> Restringe para grade (SELECT) - <> -
- - {carregandoHorarios ? ( - - ) : ( - - )} -
- -
- - -
- - ) : ( - // ESTADO 1: Médico ou Data PENDENTE -> Permite entrada de tempo livre (INPUT TYPE="TIME") - <> -
- - -
-
- - -
- - )} - -
- - -
-
-
- -
+
- - + +
-
-
+
-
- - +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ + +
+ + +
+
+ + +
+ +
+
); }; -export default FormNovaConsulta; +export default FormNovaConsulta; \ No newline at end of file diff --git a/src/components/doctors/DoctorForm.jsx b/src/components/doctors/DoctorForm.jsx index 4de5b56f..45ec0bf2 100644 --- a/src/components/doctors/DoctorForm.jsx +++ b/src/components/doctors/DoctorForm.jsx @@ -1,35 +1,37 @@ -import React, { useState, useRef } from "react"; -import { Link, useNavigate, useLocation } from "react-router-dom"; +import React, { useState, useRef } from 'react'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { const navigate = useNavigate(); const location = useLocation(); const FormatTelefones = (valor) => { - const digits = String(valor).replace(/\D/g, "").slice(0, 11); + const digits = String(valor).replace(/\D/g, '').slice(0, 11); return digits - .replace(/(\d)/, "($1") - .replace(/(\d{2})(\d)/, "$1) $2") - .replace(/(\d)(\d{4})/, "$1 $2") - .replace(/(\d{4})(\d{4})/, "$1-$2"); + .replace(/(\d)/, '($1') + .replace(/(\d{2})(\d)/, '$1) $2') + .replace(/(\d)(\d{4})/, '$1 $2') + .replace(/(\d{4})(\d{4})/, '$1-$2'); }; const FormatCPF = (valor) => { - const digits = String(valor).replace(/\D/g, "").slice(0, 11); + const digits = String(valor).replace(/\D/g, '').slice(0, 11); return digits - .replace(/(\d{3})(\d)/, "$1.$2") - .replace(/(\d{3})(\d)/, "$1.$2") - .replace(/(\d{3})(\d{1,2})$/, "$1-$2"); + .replace(/(\d{3})(\d)/, '$1.$2') + .replace(/(\d{3})(\d)/, '$1.$2') + .replace(/(\d{3})(\d{1,2})$/, '$1-$2'); }; + const validarCPF = (cpf) => { - const cpfLimpo = cpf.replace(/\D/g, ""); - + const cpfLimpo = cpf.replace(/\D/g, ''); + if (cpfLimpo.length !== 11) return false; if (/^(\d)\1+$/.test(cpfLimpo)) return false; - + + let soma = 0; for (let i = 0; i < 9; i++) { soma += parseInt(cpfLimpo.charAt(i)) * (10 - i); @@ -44,17 +46,15 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { } resto = 11 - (soma % 11); let digito2 = resto === 10 || resto === 11 ? 0 : resto; - - return ( - digito1 === parseInt(cpfLimpo.charAt(9)) && - digito2 === parseInt(cpfLimpo.charAt(10)) - ); + + + return digito1 === parseInt(cpfLimpo.charAt(9)) && digito2 === parseInt(cpfLimpo.charAt(10)); }; const [avatarUrl, setAvatarUrl] = useState(null); const [showRequiredModal, setShowRequiredModal] = useState(false); const [emptyFields, setEmptyFields] = useState([]); - const [cpfError, setCpfError] = useState(""); + const [cpfError, setCpfError] = useState(''); const nomeRef = useRef(null); const cpfRef = useRef(null); @@ -70,59 +70,63 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { }); const handleToggleCollapse = (section) => { - setCollapsedSections((prevState) => ({ + setCollapsedSections(prevState => ({ ...prevState, - [section]: !prevState[section], + [section]: !prevState[section] })); }; const handleChange = (e) => { const { name, value, type, checked, files } = e.target; + if (value && emptyFields.includes(name)) { - setEmptyFields((prev) => prev.filter((field) => field !== name)); + setEmptyFields(prev => prev.filter(field => field !== name)); } - if (name === "cpf" && cpfError) { - setCpfError(""); + + if (name === 'cpf' && cpfError) { + setCpfError(''); } - if (type === "checkbox") { - setFormData((prev) => ({ ...prev, [name]: checked })); - } else if (type === "file") { - setFormData((prev) => ({ ...prev, [name]: files[0] })); + if (type === 'checkbox') { + setFormData(prev => ({ ...prev, [name]: checked })); + } else if (type === 'file') { + setFormData(prev => ({ ...prev, [name]: files[0] })); - if (name === "foto" && files[0]) { + if (name === 'foto' && files[0]) { const reader = new FileReader(); reader.onloadend = () => { setAvatarUrl(reader.result); }; reader.readAsDataURL(files[0]); - } else if (name === "foto" && !files[0]) { + } else if (name === 'foto' && !files[0]) { setAvatarUrl(null); } - } else if (name.includes("cpf")) { - let cpfFormatado = FormatCPF(value); - setFormData((prev) => ({ ...prev, [name]: cpfFormatado })); - const cpfLimpo = cpfFormatado.replace(/\D/g, ""); + } else if (name.includes('cpf')) { + let cpfFormatado = FormatCPF(value); + setFormData(prev => ({ ...prev, [name]: cpfFormatado })); + + + const cpfLimpo = cpfFormatado.replace(/\D/g, ''); if (cpfLimpo.length === 11) { if (!validarCPF(cpfFormatado)) { - setCpfError("CPF inválido"); + setCpfError('CPF inválido'); } else { - setCpfError(""); + setCpfError(''); } } - } else if (name.includes("phone")) { + } else if (name.includes('phone')) { let telefoneFormatado = FormatTelefones(value); - setFormData((prev) => ({ ...prev, [name]: telefoneFormatado })); + setFormData(prev => ({ ...prev, [name]: telefoneFormatado })); } else { - setFormData((prev) => ({ ...prev, [name]: value })); + setFormData(prev => ({ ...prev, [name]: value })); } }; const handleCepBlur = async () => { - const cep = formData.cep?.replace(/\D/g, ""); + const cep = formData.cep?.replace(/\D/g, ''); if (cep && cep.length === 8) { try { const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`); @@ -130,49 +134,50 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { if (!data.erro) { setFormData((prev) => ({ ...prev, - street: data.logradouro || "", - neighborhood: data.bairro || "", - city: data.localidade || "", - state: data.uf || "", + street: data.logradouro || '', + neighborhood: data.bairro || '', + city: data.localidade || '', + state: data.uf || '' })); } else { setShowRequiredModal(true); - setEmptyFields(["cep"]); + setEmptyFields(['cep']); } } catch (error) { setShowRequiredModal(true); - setEmptyFields(["cep"]); + setEmptyFields(['cep']); } } }; + const scrollToEmptyField = (fieldName) => { let fieldRef = null; - + switch (fieldName) { - case "full_name": + case 'full_name': fieldRef = nomeRef; - setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); break; - case "cpf": + case 'cpf': fieldRef = cpfRef; - setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); break; - case "email": + case 'email': fieldRef = emailRef; - setCollapsedSections((prev) => ({ ...prev, contato: true })); + setCollapsedSections(prev => ({ ...prev, contato: true })); break; - case "phone_mobile": + case 'phone_mobile': fieldRef = telefoneRef; - setCollapsedSections((prev) => ({ ...prev, contato: true })); + setCollapsedSections(prev => ({ ...prev, contato: true })); break; - case "crm_uf": + case 'crm_uf': fieldRef = crmUfRef; - setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); break; - case "crm": + case 'crm': fieldRef = crmRef; - setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true })); + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); break; default: return; @@ -181,20 +186,21 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { setTimeout(() => { if (fieldRef.current) { - fieldRef.current.scrollIntoView({ - behavior: "smooth", - block: "center", + fieldRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center' }); fieldRef.current.focus(); - - fieldRef.current.style.border = "2px solid #dc3545"; - fieldRef.current.style.boxShadow = - "0 0 0 0.2rem rgba(220, 53, 69, 0.25)"; - + + + fieldRef.current.style.border = '2px solid #dc3545'; + fieldRef.current.style.boxShadow = '0 0 0 0.2rem rgba(220, 53, 69, 0.25)'; + + setTimeout(() => { if (fieldRef.current) { - fieldRef.current.style.border = ""; - fieldRef.current.style.boxShadow = ""; + fieldRef.current.style.border = ''; + fieldRef.current.style.boxShadow = ''; } }, 3000); } @@ -204,17 +210,18 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { const handleSubmit = async () => { const missingFields = []; - if (!formData.full_name) missingFields.push("full_name"); - if (!formData.cpf) missingFields.push("cpf"); - if (!formData.email) missingFields.push("email"); - if (!formData.phone_mobile) missingFields.push("phone_mobile"); - if (!formData.crm_uf) missingFields.push("crm_uf"); - if (!formData.crm) missingFields.push("crm"); + if (!formData.full_name) missingFields.push('full_name'); + if (!formData.cpf) missingFields.push('cpf'); + if (!formData.email) missingFields.push('email'); + if (!formData.phone_mobile) missingFields.push('phone_mobile'); + if (!formData.crm_uf) missingFields.push('crm_uf'); + if (!formData.crm) missingFields.push('crm'); if (missingFields.length > 0) { setEmptyFields(missingFields); setShowRequiredModal(true); - + + setTimeout(() => { if (missingFields.length > 0) { scrollToEmptyField(missingFields[0]); @@ -223,23 +230,26 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { return; } - const cpfLimpo = formData.cpf.replace(/\D/g, ""); + + const cpfLimpo = formData.cpf.replace(/\D/g, ''); if (cpfLimpo.length !== 11) { setShowRequiredModal(true); - setEmptyFields(["cpf"]); - setCpfError("CPF deve ter 11 dígitos"); - setTimeout(() => scrollToEmptyField("cpf"), 500); + setEmptyFields(['cpf']); + setCpfError('CPF deve ter 11 dígitos'); + setTimeout(() => scrollToEmptyField('cpf'), 500); return; } + if (!validarCPF(formData.cpf)) { setShowRequiredModal(true); - setEmptyFields(["cpf"]); - setCpfError("CPF inválido"); - setTimeout(() => scrollToEmptyField("cpf"), 500); + setEmptyFields(['cpf']); + setCpfError('CPF inválido'); + setTimeout(() => scrollToEmptyField('cpf'), 500); return; } + try { await onSave({ ...formData }); @@ -290,16 +300,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { alignItems: "center", }} > -
- Atenção -
+
Atenção
- + - @@ -848,79 +631,4 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { ); } -function WeeklyAvailabilityPicker() { - const days = ["Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"]; - const [availability, setAvailability] = useState( - days.reduce((acc, day) => ({ ...acc, [day]: [] }), {}) - ); - - const handleAddInterval = (day) => { - const newIntervals = [...availability[day], { start: "", end: "" }]; - const newAvailability = { ...availability, [day]: newIntervals }; - setAvailability(newAvailability); - }; - - const handleRemoveInterval = (day, index) => { - const newIntervals = availability[day].filter((_, i) => i !== index); - setAvailability({ ...availability, [day]: newIntervals }); - }; - - const handleTimeChange = (day, index, field, value) => { - const newIntervals = availability[day].map((interval, i) => - i === index ? { ...interval, [field]: value } : interval - ); - setAvailability({ ...availability, [day]: newIntervals }); - }; - - const handleSave = () => { - const data = []; - for (const [day, intervals] of Object.entries(availability)) { - intervals.forEach(({ start, end }) => { - const dayIndex = days.indexOf(day); - data.push({ day: dayIndex, start, end }); - }); - } - - fetch("https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - }) - .then((res) => res.json()) - .then((res) => console.log("Salvo:", res)) - .catch((err) => console.error("Erro:", err)); - }; - - return ( -
- {days.map((day) => ( -
-
{day}
- {availability[day].map((interval, index) => ( -
- handleTimeChange(day, index, "start", e.target.value)} - /> - até - handleTimeChange(day, index, "end", e.target.value)} - /> - -
- ))} - -
- ))} - -
- ); -} - export default DoctorForm; \ No newline at end of file diff --git a/src/pages/DoctorCadastroManager.jsx b/src/pages/DoctorCadastroManager.jsx index eb97f566..450e7f75 100644 --- a/src/pages/DoctorCadastroManager.jsx +++ b/src/pages/DoctorCadastroManager.jsx @@ -44,7 +44,6 @@ function DoctorCadastroManager() { number: doctorData.number || null, complement: doctorData.complement || null, phone2: doctorData.phone2 ? doctorData.phone2.replace(/\D/g, '') : null, - availability: doctorData.availability || {}, }; console.log('Dados limpos para envio:', cleanedData); diff --git a/src/pages/DoctorEditPage.jsx b/src/pages/DoctorEditPage.jsx index d5bd701d..eecfb147 100644 --- a/src/pages/DoctorEditPage.jsx +++ b/src/pages/DoctorEditPage.jsx @@ -1,115 +1,77 @@ -import React, { useEffect, useState } from "react"; -import { GetDoctorByID } from "../components/utils/Functions-Endpoints/Doctor"; -import DoctorForm from "../components/doctors/DoctorForm"; -import { useAuth } from "../components/utils/AuthProvider"; -import { useParams } from "react-router-dom"; -import API_KEY from "../components/utils/apiKeys"; - +import React from 'react' +import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor' +import DoctorForm from '../components/doctors/DoctorForm' +import { useAuth } from '../components/utils/AuthProvider' +import {useEffect, useState} from 'react' +import { useParams } from 'react-router-dom' +import API_KEY from '../components/utils/apiKeys' const DoctorEditPage = () => { - const { getAuthorizationHeader } = useAuth(); - const [DoctorToPUT, setDoctorPUT] = useState({}); - const [availability, setAvailability] = useState([]); - const { id: DoctorID } = useParams(); + const {getAuthorizationHeader, isAuthenticated} = useAuth(); + const [DoctorToPUT, setDoctorPUT] = useState({}) + + const Parametros = useParams() - useEffect(() => { - const authHeader = getAuthorizationHeader(); + const DoctorID = Parametros.id - // Buscar médico - GetDoctorByID(DoctorID, authHeader) - .then((data) => setDoctorPUT(data[0])) - .catch((err) => console.error(err)); +useEffect(() => { + + const authHeader = getAuthorizationHeader() - // Buscar disponibilidades - const fetchAvailability = async () => { - try { - const res = await fetch( - `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability/${DoctorID}`, - { headers: { apikey: API_KEY, Authorization: authHeader } } - ); - const data = await res.json(); - setAvailability(data.data || []); - } catch (err) { - console.error(err); - } - }; - - fetchAvailability(); - }, []); - - // Atualizar uma disponibilidade - const updateAvailability = async (id, updatedData) => { - const authHeader = getAuthorizationHeader(); - try { - await fetch( - `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability/${id}`, - { - method: "PUT", - headers: { - apikey: API_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - }, - body: JSON.stringify(updatedData), - } - ); - // atualizar localmente - setAvailability((prev) => - prev.map((a) => (a.id === id ? { ...a, ...updatedData } : a)) - ); - } catch (err) { - console.error(err); - } - }; - - // Deletar uma disponibilidade - const deleteAvailability = async (id) => { - const authHeader = getAuthorizationHeader(); - try { - await fetch( - `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability/${id}`, - { - method: "DELETE", - headers: { apikey: API_KEY, Authorization: authHeader }, - } - ); - setAvailability((prev) => prev.filter((a) => a.id !== id)); - } catch (err) { - console.error(err); - } - }; + GetDoctorByID(DoctorID, authHeader) + .then((data) => { + console.log(data, "médico vindo da API"); + setDoctorPUT(data[0]) + ; // supabase retorna array + }) + .catch((err) => console.error("Erro ao buscar paciente:", err)); + +}, []) const HandlePutDoctor = async () => { - const authHeader = getAuthorizationHeader(); - try { - await fetch( - `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`, - { - method: "PUT", - headers: { - apikey: API_KEY, - Authorization: authHeader, - "Content-Type": "application/json", - }, - body: JSON.stringify(DoctorToPUT), - } - ); - } catch (err) { - console.error(err); - } +const authHeader = getAuthorizationHeader() + + + var myHeaders = new Headers(); + myHeaders.append('apikey', API_KEY) + myHeaders.append("Authorization", authHeader); + myHeaders.append("Content-Type", "application/json"); + + var raw = JSON.stringify(DoctorToPUT); + + console.log("Enviando médico para atualização:", DoctorToPUT); + + var requestOptions = { + method: 'PUT', + headers: myHeaders, + body: raw, + redirect: 'follow' }; + try { + const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`,requestOptions); + console.log(response) + + } catch (error) { + console.error("Erro ao atualizar paciente:", error); + throw error; + } + + } + + return (
- -
- ); -}; -export default DoctorEditPage; + + +
+ ) +} + +export default DoctorEditPage \ No newline at end of file From 7f14cf16b7722e45ea7a1a9108cec6daed24ac0e Mon Sep 17 00:00:00 2001 From: Jessica_Faro Date: Wed, 15 Oct 2025 20:14:03 -0300 Subject: [PATCH 13/14] tiptap --- package-lock.json | 746 ++++++++++++++++++++- package.json | 3 + src/PagesMedico/DoctorRelatorioManager.jsx | 59 +- src/PagesMedico/EditPageRelatorio.jsx | 183 +++-- src/PagesMedico/TiptapEditor.jsx | 74 ++ src/PagesMedico/TiptapViewer.jsx | 15 + 6 files changed, 1003 insertions(+), 77 deletions(-) create mode 100644 src/PagesMedico/TiptapEditor.jsx create mode 100644 src/PagesMedico/TiptapViewer.jsx diff --git a/package-lock.json b/package-lock.json index 068a8fa4..26a38b08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,9 @@ "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", + "@tiptap/extension-placeholder": "^3.7.1", + "@tiptap/react": "^3.7.1", + "@tiptap/starter-kit": "^3.7.1", "apexcharts": "^5.3.4", "bootstrap": "^5.3.8", "bootstrap-icons": "^1.13.1", @@ -16184,6 +16187,31 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "optional": true, + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "optional": true, + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "optional": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -17174,6 +17202,11 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/@remirror/core-constants": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==" + }, "node_modules/@restart/hooks": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", @@ -17747,6 +17780,424 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@tiptap/core": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.7.1.tgz", + "integrity": "sha512-jB6R8EGI34QUmV7EhtE+JVpjbZ6Wa0dcf0LNS36X9V7FtDQcnxl7ekRs/ftELt/6qOjubRdyhaID0wNdJVmFtw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.7.1.tgz", + "integrity": "sha512-UPIne4kD8hwhadPtapn0WfJCNiF+b3ftNYiC1BpNfti5NmM0sXuqOOC0WnVgGgsNuJp4hd+4PMp42InlD6/1aw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.7.1.tgz", + "integrity": "sha512-XZRt1blYGpqVlcBo+PKH1mlbsqdc5KsWi/ZsPBV3Ajg/Vx5d6SAY4wK6CW1SpotE1wWucUhfAmXddhBFvYzaUA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.7.1.tgz", + "integrity": "sha512-7qLK49GC7pW6FbAE6vOGphcyjq7CqiBEwr9i9/5UgnadjLtREDzBl28D+95+8TkyF7sM3hP6s6RU+nh87v5fqw==", + "optional": true, + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1", + "@tiptap/pm": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.7.1.tgz", + "integrity": "sha512-AO7EVAftvzSw7Sftp36P+HNedxjygMpobYNTBQzHfGljRZh8VDhIUzwyP1OsmlrcCbBxsrjMZLrmk/ozsALq0g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.7.1.tgz", + "integrity": "sha512-ZRarYvgQ16ZrzKox/iW3bVr5IVNBsD0yjU5S7GVmlRgRQ8lhsTloLk9Gu05uuZ6dOoL3qApLA8+W7w8sxZJ35w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.7.1.tgz", + "integrity": "sha512-/Ov81QXEn6AOiiSUFlM57a+YSye/Lkhvgy303+CEGtDuFVU/SJ0tDsgmSYzkP5q6DIVQLAXp5WkxEo02GnYHgQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1", + "@tiptap/pm": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.7.1.tgz", + "integrity": "sha512-b7NHWseJSvhhbsiSWjQgiJcs6FUJiEJocfhazDiWAOk5ELQ6+oiIe7ecEgDqBmafk9oziV9r7u9OAgyeyP3JBA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.7.1.tgz", + "integrity": "sha512-wZT3bPeNJAasOvNr6tUZAwXFeKlQEToSnVAjFiBzJwLDonuK8ZaAiBCDQgqEQSlP3HsEE4/qkERBNrdyAT26CQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.7.1.tgz", + "integrity": "sha512-epfA87IIBy5IREMjmlRskp8T/9/avjfM8RtcqDnKQxVVXn8yl5i0Pca0jXD4w7rIAS7G95N9sYRxsou6Y6fTQg==", + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "^3.7.1", + "@tiptap/pm": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.7.1.tgz", + "integrity": "sha512-1UrZEaqruWPLdgYsAm4au7BAyTDjaNRP0E7UIoEoGsq+MAS2MM3g4suXMzu+l3ZIayrSy98N3T8DIUG+U6+mww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.7.1.tgz", + "integrity": "sha512-pEvRjWexMNxXH5FOy3EhzyMFDFHrRTWOgZbWAxliKDg2dFEJ50e9KcCMDs87e7++V753lEKnFTmz/9WaH7cwcQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.7.1.tgz", + "integrity": "sha512-rOUou6b0+5E+DAmEMTC/mlKTLiOr4D0LKzBfqBLQ3zUyZPZabOKzN0L+4MaLNR2CkXy/Ae4du5ucHGrGOWzVrQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.7.1.tgz", + "integrity": "sha512-f4lXW/LHuJBF11PIrWdNAzTmlapV4fVujJ5eCsLAkpzhx3izVrDW/WlKRrkGUCy/qQT4v7BbHNa5JYlKDzDo0Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1", + "@tiptap/pm": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.7.1.tgz", + "integrity": "sha512-Bm6eOtcafc5kjE357GlvIY2hNTRRAkb8D5SRm8zYlVB0fiLto+r15Ht+DTOmLiQKEGtEArQ/C8Rh2j09UdH2vA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.7.1.tgz", + "integrity": "sha512-6+0/mo+EKDiA1d1pDZSsf/51ZOwdFnN35yF/4celxdr/JL4aupvtttIjGAtWd37h50cadYSL4F1uacKs7yyh8Q==", + "dependencies": { + "linkifyjs": "^4.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1", + "@tiptap/pm": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-list": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.7.1.tgz", + "integrity": "sha512-E93oXkV2vsZThsix0OA7RiHNLIMGi+w9ASKZ+8TGB69oy32yujnnZz6YVhTVVDPOw8rCP5CnOPhJbgdcqByr0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1", + "@tiptap/pm": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.7.1.tgz", + "integrity": "sha512-qkXfWRBusJCId9VhRo9vihcrmxvJ83fkzYWI0LiefJCT1LKfMaeInFNxIsFeUU4q9nR0mhZo7ES3E2+Tk0U3Mw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-list-keymap": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.7.1.tgz", + "integrity": "sha512-3WyzWge/g6FoxMTkoAARtMJyIYQbpclNX48HyAqdwjJXuLmz3qckEnJEXo47CvJlRsNAlcDJniRS9j5SVJupRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.7.1.tgz", + "integrity": "sha512-iX3DhTwFp84fiCNSF7+kl/sq6orXq2QFcV2AH+CvL+d0WW1STYmmVmE26gHEjyY82QfpvLZYUCEG6RSYpxFIZw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.7.1.tgz", + "integrity": "sha512-L5dsppKKo46MN3Go5vzqqzjPX89pz1lIkIUN3IhU+KmAHg1TklfR7FQkiIFIIV2rb2ZLuLpD/JcNsZAUmJTW5Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-placeholder": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.7.1.tgz", + "integrity": "sha512-35VZ578c9oho62Cpupzo9rZCxhPNtuLHNnpKy7bXYaR0Quy3/axtnly1x09nS/KZ4oqw8rvYtkS6GAXwMKKtLg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.7.1.tgz", + "integrity": "sha512-Ctqk/SfmGd3hFCDr4/OH0Dnja19UWUrUEY62pwM7JCkbY/Y9QwPLSO32L6KyamwUDek9SL/ATjRPz6GLp0P7hg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.7.1.tgz", + "integrity": "sha512-m+8FJrFAllJYuzLbEXJ9AztobxmWBTjWorkHcMHBLAbY2ytmAhIM1u3ExtOn9DjvnIT6MffCaq0i/KjhSBYJlA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.7.1.tgz", + "integrity": "sha512-tyx7ZM2ll8DclKe9Ea/vPyqaZBgnJfIbKBOpecpzawDaJ5ocjwywmYNduevOhw327X2/i8LIQBsPuIOJselcUQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1" + } + }, + "node_modules/@tiptap/extensions": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.7.1.tgz", + "integrity": "sha512-O7eq3frqh7kn/J2P+lpx8blBQrIQxt21J3NvlQJhW5nXIECdo2ox8SQcEfli0EqMSwZCZTdVufdFBkWfIRXhRg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1", + "@tiptap/pm": "^3.7.1" + } + }, + "node_modules/@tiptap/pm": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.7.1.tgz", + "integrity": "sha512-t3n054kplRtRYn8pDnzF/prDUccF7QX7jPYLsYBpLn3+d59J5KKkBmOpPExUGE8kZkNoLfwffAFj6NBfqOu+Xg==", + "dependencies": { + "prosemirror-changeset": "^2.3.0", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.1", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.24.1", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.5.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.4", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.38.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.7.1.tgz", + "integrity": "sha512-0tDih8ACrVhKCpNjHcQhxV5DBsyY4xpAjr8nfb2RK+J2l1lGtNuj3PDRHlDLroQiK8svwARz4hsBs8MYWKSkfg==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "fast-deep-equal": "^3.1.3", + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "optionalDependencies": { + "@tiptap/extension-bubble-menu": "^3.7.1", + "@tiptap/extension-floating-menu": "^3.7.1" + }, + "peerDependencies": { + "@tiptap/core": "^3.7.1", + "@tiptap/pm": "^3.7.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.7.1.tgz", + "integrity": "sha512-ZYgA3BkASQmHyoDlUYKFPEpCzIcn/FP/Sb+Ic2L7gt2gOC7zvWAVc/2yIbiFuq+48s+5U/KJqDiXn2hiLwXxpA==", + "dependencies": { + "@tiptap/core": "^3.7.1", + "@tiptap/extension-blockquote": "^3.7.1", + "@tiptap/extension-bold": "^3.7.1", + "@tiptap/extension-bullet-list": "^3.7.1", + "@tiptap/extension-code": "^3.7.1", + "@tiptap/extension-code-block": "^3.7.1", + "@tiptap/extension-document": "^3.7.1", + "@tiptap/extension-dropcursor": "^3.7.1", + "@tiptap/extension-gapcursor": "^3.7.1", + "@tiptap/extension-hard-break": "^3.7.1", + "@tiptap/extension-heading": "^3.7.1", + "@tiptap/extension-horizontal-rule": "^3.7.1", + "@tiptap/extension-italic": "^3.7.1", + "@tiptap/extension-link": "^3.7.1", + "@tiptap/extension-list": "^3.7.1", + "@tiptap/extension-list-item": "^3.7.1", + "@tiptap/extension-list-keymap": "^3.7.1", + "@tiptap/extension-ordered-list": "^3.7.1", + "@tiptap/extension-paragraph": "^3.7.1", + "@tiptap/extension-strike": "^3.7.1", + "@tiptap/extension-text": "^3.7.1", + "@tiptap/extension-underline": "^3.7.1", + "@tiptap/extensions": "^3.7.1", + "@tiptap/pm": "^3.7.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -18078,6 +18529,20 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "license": "MIT" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -18088,6 +18553,11 @@ "@types/unist": "*" } }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -18184,14 +18654,22 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", - "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", - "license": "MIT", + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "dependencies": { "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", + "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", + "peer": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.12", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", @@ -21060,6 +21538,11 @@ "node": ">=10" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -26850,6 +27333,19 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/linkifyjs": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", + "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==" + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -27041,6 +27537,38 @@ "tmpl": "1.0.5" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -27329,6 +27857,11 @@ "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", "license": "CC0-1.0" }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -28526,6 +29059,11 @@ "node": ">= 0.8.0" } }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -30322,6 +30860,183 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/prosemirror-changeset": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz", + "integrity": "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz", + "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", + "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.2.tgz", + "integrity": "sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz", + "integrity": "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.3", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.3.tgz", + "integrity": "sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA==", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", + "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", + "dependencies": { + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", + "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.1.tgz", + "integrity": "sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==", + "dependencies": { + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.25.0", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.10.3", + "prosemirror-view": "^1.39.1" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.4.tgz", + "integrity": "sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.3", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.3.tgz", + "integrity": "sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -30365,6 +31080,14 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -31707,6 +32430,11 @@ "randombytes": "^2.1.0" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -33802,6 +34530,11 @@ "node": ">=4.2.0" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -34250,6 +34983,11 @@ "browser-process-hrtime": "^1.0.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/w3c-xmlserializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", diff --git a/package.json b/package.json index 8f30168a..728f80ba 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", + "@tiptap/extension-placeholder": "^3.7.1", + "@tiptap/react": "^3.7.1", + "@tiptap/starter-kit": "^3.7.1", "apexcharts": "^5.3.4", "bootstrap": "^5.3.8", "bootstrap-icons": "^1.13.1", diff --git a/src/PagesMedico/DoctorRelatorioManager.jsx b/src/PagesMedico/DoctorRelatorioManager.jsx index ce246021..22a79b27 100644 --- a/src/PagesMedico/DoctorRelatorioManager.jsx +++ b/src/PagesMedico/DoctorRelatorioManager.jsx @@ -5,6 +5,8 @@ import { useAuth } from '../components/utils/AuthProvider'; import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient'; import { useNavigate } from 'react-router-dom'; import html2pdf from 'html2pdf.js'; +import TiptapViewer from './TiptapViewer'; + const DoctorRelatorioManager = () => { const navigate = useNavigate() const {getAuthorizationHeader} = useAuth(); @@ -13,7 +15,7 @@ const DoctorRelatorioManager = () => { const [PacientesComRelatorios, setPacientesComRelatorios] = useState([]) const [showModal, setShowModal] = useState(false) const [index, setIndex] = useState() - + // 1º useEffect: Busca os dados dos pacientes após carregar os relatórios useEffect( () => { let pacientesDosRelatorios = [] @@ -26,34 +28,32 @@ const DoctorRelatorioManager = () => { if (paciente.length > 0) { pacientesDosRelatorios.push(paciente[0]); } - } setPacientesComRelatorios(pacientesDosRelatorios); - } - ListarPacientes() - console.log(PacientesComRelatorios, 'aqui') - - }, [RelatoriosFiltrados]); - + + }, [RelatoriosFiltrados, authHeader]); + // NOVO: useEffect para logar PacientesComRelatorios após a atualização + useEffect(() => { + console.log(PacientesComRelatorios, 'aqui') + }, [PacientesComRelatorios]) + + // 2º useEffect: Busca a lista de relatórios useEffect(() => { var myHeaders = new Headers(); myHeaders.append("apikey", API_KEY); myHeaders.append("Authorization", authHeader); - var requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' }; - fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&status", requestOptions) .then(response => response.json()) .then(data => { setRelatorios(data); console.log(data) }) .catch(error => console.log('error', error)); - }, []) - + }, [authHeader]) const BaixarPDFdoRelatorio = (nome_paciente) => { const elemento = document.getElementById("folhaA4"); // tua div do relatório const opt = { @@ -62,10 +62,8 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu html2canvas: { scale: 2 }, jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }, }; - html2pdf().set(opt).from(elemento).save(); } - return (
{showModal && ( @@ -82,36 +80,36 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu
-

Clinica Rise up

Dr - CRM/SP 123456

Avenida - (79) 9 4444-4444

-

Paciente: {PacientesComRelatorios[index]?.full_name}

Data de nascimento: {PacientesComRelatorios[index]?.birth_date}

-

Data do exame: {}

-

Exame: {RelatoriosFiltrados[index]?.exam}

- -

Diagnostico: {RelatoriosFiltrados[index]?.diagnosis}

-

Conclusão: {RelatoriosFiltrados[index]?.conclusion}

+ {/* INÍCIO DA MUDANÇA (da resposta anterior) */} +

Conteúdo do Relatório:

+ + {/* FIM DA MUDANÇA */}
-

Dr {RelatoriosFiltrados[index]?.required_by}

Emitido em: 0

-
- -
)} - -

Lista de Relatórios

@@ -143,14 +139,12 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu
-
{" "} Filtros
-
-
SegSeg Ter Qua Qui
console.log('Clicou n aconsulta')}> + diff --git a/src/components/AgendarConsulta/TabelaAgendamentoSemana.jsx b/src/components/AgendarConsulta/TabelaAgendamentoSemana.jsx index 82f26bbf..95a32380 100644 --- a/src/components/AgendarConsulta/TabelaAgendamentoSemana.jsx +++ b/src/components/AgendarConsulta/TabelaAgendamentoSemana.jsx @@ -126,22 +126,21 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes }) => { return (
{/* Container de Navegação */} -
+
- -

{tituloSemana}

- +

{tituloSemana}

@@ -166,7 +165,7 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes }) => { {/* Mapeamento de COLUNAS (dias) */}
{semanaParaRenderizar.segunda[indiceLinha] - ? + ? : null }
@@ -178,7 +171,6 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu - @@ -210,7 +202,6 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu Ver Detalhes - + ) } diff --git a/src/PagesMedico/TiptapEditor.jsx b/src/PagesMedico/TiptapEditor.jsx new file mode 100644 index 00000000..71945624 --- /dev/null +++ b/src/PagesMedico/TiptapEditor.jsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { useEditor, EditorContent } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import Link from '@tiptap/extension-link'; +// Componente da barra de menu (Menu Bar) +const MenuBar = ({ editor }) => { + if (!editor) { + return null; + } + // Estilos simples para os botões. Você pode e deve estilizar melhor com CSS/Bootstrap. + const buttonStyle = { + marginRight: '4px', + padding: '4px 8px', + cursor: 'pointer', + border: '1px solid #ccc', + borderRadius: '4px', + backgroundColor: editor.isActive('bold') || editor.isActive('italic') ? '#ddd' : 'white', + }; + return ( +
+ + + + + {/* Adicione mais botões conforme a necessidade (link, código, etc.) */} +
+ ); +}; +// Componente principal do Editor +const TiptapEditor = ({ content, onChange }) => { + const editor = useEditor({ + extensions: [ + StarterKit.configure({ + // Desativa 'hardBreak' e 'blockquote' se não forem necessários para simplificar + hardBreak: false, + }), + Link, // Adiciona suporte para links + ], + content: content || '

Inicie o relatório aqui...

', + onUpdate: ({ editor }) => { + // Quando o conteúdo muda, chama a função onChange com o HTML + onChange(editor.getHTML()); + }, + }); + return ( +
+ + +
+ ); +}; +export default TiptapEditor; \ No newline at end of file diff --git a/src/PagesMedico/TiptapViewer.jsx b/src/PagesMedico/TiptapViewer.jsx new file mode 100644 index 00000000..cf21c496 --- /dev/null +++ b/src/PagesMedico/TiptapViewer.jsx @@ -0,0 +1,15 @@ +import React from 'react'; + +// Componente para renderizar o HTML salvo (conteúdo do editor) +const TiptapViewer = ({ htmlContent }) => { + return ( + // 'dangerouslySetInnerHTML' é necessário para renderizar HTML +
+ ); +}; + +export default TiptapViewer; \ No newline at end of file From 25964088af7512468f4349afe4a8ec2e16fa4b1e Mon Sep 17 00:00:00 2001 From: Jessica_Faro Date: Wed, 15 Oct 2025 20:30:04 -0300 Subject: [PATCH 14/14] =?UTF-8?q?resolu=C3=A7=C3=A3o=20de=20erros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/App.js b/src/App.js index 2135c6b8..0335710a 100644 --- a/src/App.js +++ b/src/App.js @@ -9,13 +9,12 @@ import LandingPage from './pages/LandingPage'; import PerfilFinanceiro from "./perfis/perfil_financeiro/PerfilFinanceiro"; import Perfiladm from "./perfis/Perfil_adm/Perfiladm"; import PerfilMedico from "./perfis/Perfil_medico/PerfilMedico"; -<<<<<<< HEAD + +// COMBINADO: Importações de ambas as versões import PerfilPaciente from "./perfis/Perfil_paciente/Perfilpaciente" -======= import ProfilePage from "./pages/ProfilePage"; import Header from "./components/Header/Header"; ->>>>>>> perfillogin // Componentes globais de acessibilidade import VlibrasWidget from "./components/VlibrasWidget"; @@ -37,15 +36,15 @@ function App() { } /> } /> } /> -<<<<<<< HEAD + + {/* COMBINADO: Rotas de ambas as versões */} } /> -======= } /> ->>>>>>> perfillogin + Página não encontrada} /> ); } -export default App; +export default App; \ No newline at end of file
Paciente CPFExame