Compare commits

..

21 Commits

Author SHA1 Message Date
joao_pedro
15062ca32e filtro de médico resolvido e mostrar os dias ao lado da data na tabela diaria 2025-10-28 08:18:20 -03:00
joao_pedro
8a3d6e0305 Impedimento de refetching e melhoria do filtro do medico 2025-10-27 10:44:09 -03:00
joao_pedro
a5cd4d3447 erros 2025-10-27 08:39:56 -03:00
joao_pedro
a8fc3eb397 Criacao manager e erro login 2025-10-25 17:04:39 -03:00
joao_pedro
95054bb9c1 Erro de login 2025-10-25 17:01:39 -03:00
joao_pedro
844ebf03a8 Mudanca para registrar paciente 2025-10-25 11:39:11 -03:00
joao_pedro
b0306125ae Melhorias no agendamento 2025-10-24 11:28:59 -03:00
38ebb5f7f3 tiptap3 2025-10-23 14:21:10 -03:00
e4386c1776 tiptap3 2025-10-23 14:18:36 -03:00
d80b13e404 Merge branch 'Disponibilidade3' 2025-10-23 13:06:12 -03:00
272be628aa Corrige API de disponibilidade e tratamento de erros 2025-10-23 12:58:13 -03:00
joao_pedro
6d18a23dd7 Melhoria editar 2025-10-23 11:47:49 -03:00
joao_pedro
1ba45e6c67 Botoes editar e apagar 2025-10-23 11:25:10 -03:00
joao_pedro
cf3ea901b8 Alteracoes nos agendamenos 2025-10-23 10:11:55 -03:00
joao_pedro
aa399d2d99 merge com melhoriasAgendamentos 2025-10-23 06:40:51 -03:00
joao_pedro
191e997180 Merge com fetchErrosAPI 2025-10-23 06:39:38 -03:00
joao_pedro
8878ad6ced Criacao de uma mensagem para o erro 2025-10-23 06:34:25 -03:00
ffa909734f endpoints de hugo 2025-10-22 23:20:49 -03:00
joao_pedro
c6523f1b0d Solucao para token expirado 2025-10-22 10:32:06 -03:00
joao_pedro
edfe985512 Problema de token expirado resolvido e modal dos erros 2025-10-21 09:14:58 -03:00
joao_pedro
fe488e4dd0 configuracoes iniciais 2025-10-20 08:57:45 -03:00
34 changed files with 2398 additions and 1123 deletions

View File

@ -1,56 +0,0 @@
3993097 (HEAD -> main) Merge branch 'main' of https://git.popcode.com.br/RiseUP/riseup-squad23
63659b6 Verificação do cpf e colocar o erro 404
ecae83c (riseup/main, riseup/HEAD, origin/main, origin/HEAD) Merge pull request 'Conectando-o-resto-das-API' (#2) from Conectando-o-resto-das-API into main
908d545 (riseup/Conectando-o-resto-das-API, origin/Conectando-o-resto-das-API) feat: adicionar upload e delete de anexos do paciente
4b404c0 Merge branch 'main' of https://git.popcode.com.br/RiseUP/riseup-squad23
bd20c2d Merge remote-tracking branch 'origin/main'
8aeabd1 (riseup/Fix-dos-erros-do-projeto, origin/Fix-dos-erros-do-projeto) FIx: todos os erros que aparecia no console foram resolvidos
0e29e7d melhorias na organização de pastas
98f076a Mergin com TableMelhorias
589d590 Mergin com novas alterações de laudo
7b28e2a Details melhorias
9480edc (riseup/PaginaDetalhes, origin/PaginaDetalhes) Pàgina detalhes
e4515cf Adição das cores nos cards de consulta
d3dd2fd (riseup/TableMelhorias, origin/TableMelhorias) Detalhe nas tabelas
a54b119 Delete Anexos apos pacientes forem excluidos
6e93cb5 atualizar paciente
b9a35be começo do concerto do editar
82469bc Details funcional
cdfe4ea Validação de CPF
57c8f67 (riseup/DetalhesMedico, origin/DetalhesMedico) Detalhes do medico
b021444 Mudanças formularios e detalhes
d5d03b0 (riseup/mudanças-de-laudo, origin/mudanças-de-laudo) atualização do laudo
a502bbd agendamentos no incio
8e1fcd9 Merge branch 'feature/novo-cadastro-paciente'
bea9076 Merge remote-tracking branch 'origin/PaginaDetalhes'
e35f217 mergin branch inicio com main
1af8268 Atualizacão do laudo
725d60d feat: ajeitei o nome
bab85ff (riseup/AgendamentoSidebar, origin/AgendamentoSidebar) Concertar Agendamento
b2707e3 Refatora o estilo do formulário do paciente para uma aparência de cartão com tipografia maior
37e8959 Refatora o estilo do formulário do paciente para uma aparência de cartão com tipografia maior
0930385 feat: uma piquena mudança
f6a19c4 feat: Adiciona formulário de cadastro de paciente
d91b5cf form de agendar consulta melhorado
0a60dd7 Tabela semana e mes
7f07950 (riseup/feature-Melhoria-no-Dashboard, origin/feature-Melhoria-no-Dashboard) feat: Criação da página início e melhoria na navegação
39e25ad Pagina de detalhes atualizada
4f84791 pequenas mudanaças na tabela de semana e mes
6737955 form para nova consulta e tabelas de horario
26ded17 Nova pagina de detalhes
874de84 Inicio do agendamento
f3e7470 (riseup/gerenciamento-de-laudo, origin/gerenciamento-de-laudo) Laudo do Paciente
709cd4e Merge finalizado
d6b3e86 Merge detalhes-do-pacientes para main
08ffa55 Merge remote-tracking branch 'origin/CrudMedico'
70c4d5f Termino da organização
edd567d Inicio da organização
9c09113 Mudanças pos feedback de davi
aa3a5fa Criação da página dos detalhes dos pacientes
5534568 Inicio de detalhes e atualização do paciente
06ff7d5 Funcionalidade de delete e botão de opções
5b63fa2 Mascara telefones
fb9d783 adição da mascara do CPF
a489d84 metodo GET e POST
4eaabbd first commit
a244691 Initial commit

768
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,9 +10,11 @@
"@testing-library/jest-dom": "^6.8.0", "@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@tiptap/core": "^3.7.2",
"@tiptap/extension-placeholder": "^3.7.1", "@tiptap/extension-placeholder": "^3.7.1",
"@tiptap/pm": "^3.7.2",
"@tiptap/react": "^3.7.1", "@tiptap/react": "^3.7.1",
"@tiptap/starter-kit": "^3.7.1", "@tiptap/starter-kit": "^3.7.2",
"apexcharts": "^5.3.4", "apexcharts": "^5.3.4",
"bootstrap": "^5.3.8", "bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1", "bootstrap-icons": "^1.13.1",

View File

@ -1,82 +1,97 @@
// src/PagesMedico/DoctorRelatorioManager.jsx
import API_KEY from '../components/utils/apiKeys'; import API_KEY from '../components/utils/apiKeys';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import {useState, useEffect} from 'react' import { useState, useEffect } from 'react';
import { useAuth } from '../components/utils/AuthProvider'; import { useAuth } from '../components/utils/AuthProvider';
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient'; import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient';
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import html2pdf from 'html2pdf.js'; import html2pdf from 'html2pdf.js';
import TiptapViewer from './TiptapViewer'; import TiptapViewer from './TiptapViewer';
const DoctorRelatorioManager = () => { const DoctorRelatorioManager = () => {
const navigate = useNavigate() const navigate = useNavigate();
const {getAuthorizationHeader} = useAuth(); const { getAuthorizationHeader } = useAuth();
let authHeader = getAuthorizationHeader() const authHeader = getAuthorizationHeader();
const [RelatoriosFiltrados, setRelatorios] = useState([]) const [RelatoriosFiltrados, setRelatorios] = useState([]);
const [PacientesComRelatorios, setPacientesComRelatorios] = useState([]) const [PacientesComRelatorios, setPacientesComRelatorios] = useState([]);
const [showModal, setShowModal] = useState(false) const [MedicosComRelatorios, setMedicosComRelatorios] = useState([]);
const [index, setIndex] = useState() const [showModal, setShowModal] = useState(false);
// 1º useEffect: Busca os dados dos pacientes após carregar os relatórios const [index, setIndex] = useState();
useEffect( () => {
let pacientesDosRelatorios = []
const ListarPacientes = async () => { // busca lista de relatórios
for (let i = 0; i < RelatoriosFiltrados.length; i++) {
let relatorio = RelatoriosFiltrados[i];
let paciente_id = relatorio.patient_id;
const paciente = await GetPatientByID(paciente_id, authHeader);
console.log(paciente)
if (paciente.length > 0) {
pacientesDosRelatorios.push(paciente[0]);
}
}
setPacientesComRelatorios(pacientesDosRelatorios);
}
ListarPacientes()
}, [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(() => { useEffect(() => {
const fetchReports = async () => {
try {
var myHeaders = new Headers(); var myHeaders = new Headers();
myHeaders.append("apikey", API_KEY); myHeaders.append('apikey', API_KEY);
myHeaders.append("Authorization", authHeader); myHeaders.append('Authorization', authHeader);
var requestOptions = { var requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
method: 'GET',
headers: myHeaders, const res = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*", requestOptions);
redirect: 'follow' const data = await res.json();
}; setRelatorios(data || []);
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&status", requestOptions) } catch (err) {
.then(response => response.json()) console.error('Erro listar relatórios', err);
.then(data => { setRelatorios(data); console.log(data) }) setRelatorios([]);
.catch(error => console.log('error', error));
}, [authHeader])
const BaixarPDFdoRelatorio = (nome_paciente) => {
const elemento = document.getElementById("folhaA4"); // tua div do relatório
const opt = {
margin: 0,
filename: `relatorio_${nome_paciente || "paciente"}.pdf`,
html2canvas: { scale: 2 },
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
};
html2pdf().set(opt).from(elemento).save();
} }
};
fetchReports();
}, [authHeader]);
// depois que RelatoriosFiltrados mudar, busca pacientes e médicos correspondentes
useEffect(() => {
const fetchRelData = async () => {
const pacientes = [];
const medicos = [];
for (let i = 0; i < RelatoriosFiltrados.length; i++) {
const rel = RelatoriosFiltrados[i];
// paciente
try {
const pacienteRes = await GetPatientByID(rel.patient_id, authHeader);
pacientes.push(Array.isArray(pacienteRes) ? pacienteRes[0] : pacienteRes);
} catch (err) {
pacientes.push(null);
}
// médico: tenta created_by ou requested_by id se existir
try {
const doctorId = rel.created_by || rel.requested_by || null;
if (doctorId) {
// se created_by é id (uuid) usamos GetDoctorByID, senão se requested_by for nome, guardamos nome
const docRes = await GetDoctorByID(doctorId, authHeader);
medicos.push(Array.isArray(docRes) ? docRes[0] : docRes);
} else {
medicos.push({ full_name: rel.requested_by || '' });
}
} catch (err) {
medicos.push({ full_name: rel.requested_by || '' });
}
}
setPacientesComRelatorios(pacientes);
setMedicosComRelatorios(medicos);
};
if (RelatoriosFiltrados.length > 0) fetchRelData();
else {
setPacientesComRelatorios([]);
setMedicosComRelatorios([]);
}
}, [RelatoriosFiltrados, authHeader]);
const BaixarPDFdoRelatorio = (nome_paciente) => {
const elemento = document.getElementById("folhaA4");
const opt = { margin: 0, filename: `relatorio_${nome_paciente || "paciente"}.pdf`, html2canvas: { scale: 2 }, jsPDF: { unit: "mm", format: "a4", orientation: "portrait" } };
html2pdf().set(opt).from(elemento).save();
};
return ( return (
<div> <div>
{showModal && ( {showModal && (
<div className="modal" > <div className="modal">
<div className="modal-dialog modal-tabela-relatorio"> <div className="modal-dialog modal-tabela-relatorio">
<div className="modal-content"> <div className="modal-content">
<div className="modal-header text-white"> <div className="modal-header text-white">
<h5 className="modal-title ">Relatório de {PacientesComRelatorios[index]?.full_name} </h5> <h5 className="modal-title ">Relatório de {PacientesComRelatorios[index]?.full_name}</h5>
<button <button type="button" className="btn-close" onClick={() => setShowModal(false)}></button>
type="button"
className="btn-close"
onClick={() => setShowModal(false)}
></button>
</div> </div>
<div className="modal-body"> <div className="modal-body">
<div id="folhaA4"> <div id="folhaA4">
@ -85,46 +100,32 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu
<p>Dr - CRM/SP 123456</p> <p>Dr - CRM/SP 123456</p>
<p>Avenida - (79) 9 4444-4444</p> <p>Avenida - (79) 9 4444-4444</p>
</div> </div>
<div id='infoPaciente'> <div id='infoPaciente'>
<p>Paciente: {PacientesComRelatorios[index]?.full_name}</p> <p>Paciente: {PacientesComRelatorios[index]?.full_name}</p>
<p>Data de nascimento: {PacientesComRelatorios[index]?.birth_date} </p> <p>Data de nascimento: {PacientesComRelatorios[index]?.birth_date}</p>
<p>Data do exame: {}</p> <p>Data do exame: {RelatoriosFiltrados[index]?.due_at || ''}</p>
<p>Exame: {RelatoriosFiltrados[index]?.exam}</p> {/* Exibe conteúdo salvo (content_html) */}
{/* INÍCIO DA MUDANÇA (da resposta anterior) */}
<p style={{ marginTop: '15px', fontWeight: 'bold' }}>Conteúdo do Relatório:</p> <p style={{ marginTop: '15px', fontWeight: 'bold' }}>Conteúdo do Relatório:</p>
<TiptapViewer <TiptapViewer htmlContent={RelatoriosFiltrados[index]?.content_html || RelatoriosFiltrados[index]?.content || 'Relatório não preenchido.'} />
htmlContent={
RelatoriosFiltrados[index]?.content ||
RelatoriosFiltrados[index]?.diagnosis ||
RelatoriosFiltrados[index]?.conclusion ||
'Relatório não preenchido.'
}
/>
{/* FIM DA MUDANÇA */}
</div> </div>
<div> <div>
<p>Dr {RelatoriosFiltrados[index]?.required_by}</p> <p>Dr {MedicosComRelatorios[index]?.full_name || RelatoriosFiltrados[index]?.requested_by}</p>
<p>Emitido em: 0</p> <p>Emitido em: {RelatoriosFiltrados[index]?.created_at || '—'}</p>
</div> </div>
</div> </div>
</div> </div>
<div className="modal-footer"> <div className="modal-footer">
<button className="btn btn-primary" onClick={() => BaixarPDFdoRelatorio(PacientesComRelatorios[index]?.full_name)}><i className='bi bi-file-pdf-fill'></i> baixar em pdf</button> <button className="btn btn-primary" onClick={() => BaixarPDFdoRelatorio(PacientesComRelatorios[index]?.full_name)}><i className='bi bi-file-pdf-fill'></i> baixar em pdf</button>
<button <button type="button" className="btn btn-primary" onClick={() => { setShowModal(false) }}>Fechar</button>
type="button"
className="btn btn-primary"
onClick={() => {setShowModal(false)}}
>
Fechar
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
)} )}
<div className="page-heading">
<h3>Lista de Relatórios</h3> <div className="page-heading"><h3>Lista de Relatórios</h3></div>
</div>
<div className="page-content"> <div className="page-content">
<section className="row"> <section className="row">
<div className="col-12"> <div className="col-12">
@ -132,114 +133,59 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu
<div className="card-header d-flex justify-content-between align-items-center"> <div className="card-header d-flex justify-content-between align-items-center">
<h4 className="card-title mb-0">Relatórios Cadastrados</h4> <h4 className="card-title mb-0">Relatórios Cadastrados</h4>
<Link to={'criar'}> <Link to={'criar'}>
<button <button className="btn btn-primary"><i className="bi bi-plus-circle"></i> Adicionar Relatório</button>
className="btn btn-primary"
>
<i className="bi bi-plus-circle"></i> Adicionar Relatório
</button>
</Link> </Link>
</div> </div>
<div className="card-body"> <div className="card-body">
<div className="card p-3 mb-3"> <div className="card p-3 mb-3">
<h5 className="mb-3"> <h5 className="mb-3"><i className="bi bi-funnel-fill me-2 text-primary"></i> Filtros</h5>
<i className="bi bi-funnel-fill me-2 text-primary"></i>{" "} <div className="d-flex flex-nowrap align-items-center gap-2" style={{ overflowX: "auto", paddingBottom: "6px" }}>
Filtros <input type="text" className="form-control" placeholder="Buscar por nome..." style={{ minWidth: 250, maxWidth: 300, width: 260, flex: "0 0 auto" }} />
</h5>
<div
className="d-flex flex-nowrap align-items-center gap-2"
style={{ overflowX: "auto", paddingBottom: "6px" }}
>
<input
type="text"
className="form-control"
placeholder="Buscar por nome..."
style={{
minWidth: 250,
maxWidth: 300,
width: 260,
flex: "0 0 auto",
}}
/>
</div> </div>
</div> </div>
<div className="table-responsive"> <div className="table-responsive">
<table className="table table-striped table-hover"> <table className="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Paciente</th> <th>Paciente</th>
<th>CPF</th> <th>Doutor</th>
<th>Exame</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{RelatoriosFiltrados.length > 0 ? ( {RelatoriosFiltrados.length > 0 ? (
RelatoriosFiltrados.map((relatorio, index) => ( RelatoriosFiltrados.map((relatorio, idx) => (
<tr key={relatorio.id}> <tr key={relatorio.id}>
<td className='infos-paciente'>{PacientesComRelatorios[idx]?.full_name}</td>
<td className='infos-paciente'>{PacientesComRelatorios[index]?.full_name}</td> <td className='infos-paciente'>{MedicosComRelatorios[idx]?.full_name || relatorio.requested_by || '-'}</td>
<td className='infos-paciente'>{PacientesComRelatorios[index]?.cpf}</td>
<td>{relatorio.exam}</td>
<td> <td>
<div className="d-flex gap-2"> <div className="d-flex gap-2">
<button className="btn btn-sm" style={{ backgroundColor: "#E6F2FF", color: "#004085" }} onClick={() => { setShowModal(true); setIndex(idx); }}>
<button
className="btn btn-sm"
style={{
backgroundColor: "#E6F2FF",
color: "#004085",
}}
onClick={() => {
setShowModal(true); setIndex(index)
}}
>
<i className="bi bi-eye me-1"></i> Ver Detalhes <i className="bi bi-eye me-1"></i> Ver Detalhes
</button> </button>
<button className="btn btn-sm" style={{ backgroundColor: "#FFF3CD", color: "#856404" }} onClick={() => navigate(`/medico/relatorios/${relatorio.id}/edit`)}>
<button
className="btn btn-sm"
style={{
backgroundColor: "#FFF3CD",
color: "#856404",
}}
onClick={() => {
// MANTIDO: Uso de string template para a navegação
navigate(`/medico/relatorios/${relatorio.id}/edit`)
}}
>
<i className="bi bi-pencil me-1"></i> Editar <i className="bi bi-pencil me-1"></i> Editar
</button> </button>
</div> </div>
</td> </td>
</tr> </tr>
)) ))
) : ( ) : (
<tr> <tr><td colSpan="8" className="text-center">Nenhum paciente encontrado.</td></tr>
<td colSpan="8" className="text-center">
Nenhum paciente encontrado.
</td>
</tr>
)} )}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
</div> </div>
</div> </div>
) );
} };
export default DoctorRelatorioManager
export default DoctorRelatorioManager;

View File

@ -1,172 +1,153 @@
import React, { useEffect, useState } from 'react' // EditPageRelatorio.jsx
import { useParams, useNavigate } from 'react-router-dom' import React, { useEffect, useState } from 'react';
import API_KEY from '../components/utils/apiKeys' import { useParams, useNavigate } from 'react-router-dom';
import { useAuth } from '../components/utils/AuthProvider' import API_KEY from '../components/utils/apiKeys';
import TiptapEditor from '../PagesMedico/TiptapEditor' import { useAuth } from '../components/utils/AuthProvider';
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient' import TiptapEditor from '../PagesMedico/TiptapEditor';
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient';
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor';
const EditPageRelatorio = () => { const EditPageRelatorio = () => {
const params = useParams() const params = useParams();
const navigate = useNavigate() const navigate = useNavigate();
const {getAuthorizationHeader} = useAuth() const { getAuthorizationHeader } = useAuth();
let authHeader = getAuthorizationHeader() const authHeader = getAuthorizationHeader();
const [loading, setLoading] = useState(true);
const [relatorioData, setRelatorioData] = useState({ const [report, setReport] = useState(null);
patient_id: '', const [patient, setPatient] = useState(null);
exam: '', const [doctor, setDoctor] = useState(null);
// Mantemos apenas os campos necessários para o fetch, mas não para edição direta na UI const [html, setHtml] = useState('');
required_by: '',
address_line: '',
content: '',
})
const [loading, setLoading] = useState(true)
const [patientData, setPatientData] = useState(null) // Armazena dados do paciente
const RelatorioID = params.id
// Modelo HTML do relatório para ser carregado no Tiptap se o conteúdo for novo/vazio
const generateReportModel = (report, patient) => {
// Escapa as aspas se necessário, mas para HTML simples não é crucial
const patientName = patient?.full_name || 'Paciente não encontrado';
const birthDate = patient?.birth_date || 'Data não informada';
const exam = report?.exam || 'Exame não especificado';
const generateTemplate = (r = {}, p = {}, d = {}) => {
const patientName = p?.full_name || '[NOME DO PACIENTE]';
const birthDate = p?.birth_date || '';
const exam = r?.exam || '';
const doctorName = d?.full_name || r?.requested_by || '';
return ` return `
<div> <div>
<p style="text-align: center; font-weight: bold;">Clinica Rise up</p> <p style="text-align:center; font-weight:bold;">Clinica Rise up</p>
<p style="text-align: center;">Dr - CRM/SP 123456</p> <p style="text-align:center;">Dr - CRM/SP 123456</p>
<p style="text-align: center;">Avenida - (79) 9 4444-4444</p> <p style="text-align:center;">Avenida - (79) 9 4444-4444</p>
<br> <br/>
<p><strong>Paciente:</strong> ${patientName}</p> <p><strong>Paciente:</strong> ${patientName}</p>
<p><strong>Data de nascimento:</strong> ${birthDate}</p> <p><strong>Data de nascimento:</strong> ${birthDate}</p>
<p><strong>Data do exame:</strong> </p> <p><strong>Data do exame:</strong></p>
<p><strong>Exame:</strong> ${exam}</p> <p><strong>Exame:</strong> ${exam}</p>
<br> <br/>
<p><strong>Conteúdo do Relatório:</strong></p> <p style="font-weight:bold;">Diagnóstico:</p>
<p>1</p> <p>${r?.diagnosis || ''}</p>
<br> <br/>
<p>Dr</p> <p style="font-weight:bold;">Conclusão:</p>
<p>${r?.conclusion || ''}</p>
<br/>
<p>Dr ${doctorName}</p>
<p>Emitido em: 0</p> <p>Emitido em: 0</p>
</div> </div>
`; `;
}; };
// Função que será chamada ao salvar useEffect(() => {
const handleSave = () => { const load = async () => {
setLoading(true) setLoading(true);
var myHeaders = new Headers(); try {
const myHeaders = new Headers();
myHeaders.append("apikey", API_KEY); myHeaders.append("apikey", API_KEY);
myHeaders.append("Authorization", authHeader); myHeaders.append("Authorization", authHeader);
myHeaders.append("Content-Type", "application/json"); const requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
// Salva apenas o novo conteúdo do Tiptap (relatorioData.content) // Pega relatório por id (supabase geralmente retorna array para ?id=eq.X)
const raw = JSON.stringify({ const resp = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${params.id}`, requestOptions);
content: relatorioData.content, const data = await resp.json();
// Você pode manter order_number ou removê-lo se não for editável const rep = Array.isArray(data) ? data[0] : data;
order_number: relatorioData.order_number || 'REL-2025-4386' if (!rep) throw new Error('Relatório não encontrado');
})
var requestOptions = { setReport(rep);
// busca paciente
if (rep.patient_id) {
const p = await GetPatientByID(rep.patient_id, authHeader);
setPatient(Array.isArray(p) ? p[0] : p);
}
// busca doctor se tiver created_by/requested_by id (tentamos fallback)
if (rep.created_by) {
try {
const d = await GetDoctorByID(rep.created_by, authHeader);
setDoctor(Array.isArray(d) ? d[0] : d);
} catch (e) {
// ignore
}
}
// content_html preferencial
let initial = rep.content_html || rep.content || rep.diagnosis || rep.conclusion || '';
if (!initial || initial.trim() === '') {
initial = generateTemplate(rep, patient || {}, doctor || {});
}
setHtml(initial);
} catch (err) {
console.error('Erro carregar relatório', err);
alert('Erro ao carregar relatório. Veja console.');
} finally {
setLoading(false);
}
};
load();
// eslint-disable-next-line
}, [params.id, authHeader]);
const handleSave = async () => {
setLoading(true);
try {
const myHeaders = new Headers();
myHeaders.append('apikey', API_KEY);
myHeaders.append('Authorization', authHeader);
myHeaders.append('Content-Type', 'application/json');
const body = JSON.stringify({ content_html: html });
// supabase: PATCH via query id=eq.<id>
const res = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${params.id}`, {
method: 'PATCH', method: 'PATCH',
headers: myHeaders, headers: myHeaders,
body: raw, body
redirect: 'follow' });
};
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${RelatorioID}`, requestOptions) if (!res.ok) {
.then(response => response.text()) const txt = await res.text();
.then(result => { console.error('Erro PATCH', res.status, txt);
console.log(result); throw new Error('Erro na API');
}
alert('Relatório atualizado com sucesso!'); alert('Relatório atualizado com sucesso!');
setLoading(false) navigate('/medico/relatorios');
// MANTIDO: Volta para a área de relatórios } catch (err) {
navigate('/medico/relatorios') console.error(err);
}) alert('Erro ao salvar. Veja console.');
.catch(error => {
console.log('error', error);
alert('Erro ao atualizar o relatório.');
setLoading(false)
});
}
// Busca os dados do Relatório e do Paciente
useEffect(() => {
const fetchReportData = async () => {
var myHeaders = new Headers();
myHeaders.append("apikey", API_KEY);
myHeaders.append("Authorization", authHeader);
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
let report;
let patient;
try {
const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${RelatorioID}`, requestOptions);
const result = await response.json();
if (result.length > 0) {
report = result[0];
// Busca nome do paciente
const patientResult = await GetPatientByID(report.patient_id, authHeader);
if (patientResult.length > 0) {
patient = patientResult[0];
setPatientData(patient);
}
// Determina o conteúdo inicial
let initialContent = report.content || report.diagnosis || report.conclusion || '';
// Se o conteúdo estiver vazio, carrega o modelo do relatório completo
if (!initialContent.trim()) {
initialContent = generateReportModel(report, patient);
}
setRelatorioData({
...report,
content: initialContent,
});
}
} catch (error) {
console.log('error', error);
} finally { } finally {
setLoading(false) setLoading(false);
} }
} };
fetchReportData()
}, [RelatorioID, authHeader])
// Função para atualizar o HTML do editor if (loading) return <div>Carregando...</div>;
const handleEditorChange = (newHtml) => {
setRelatorioData(prev => ({ ...prev, content: newHtml }))
}
if (loading) {
return <div>Carregando...</div>
}
return ( return (
<div className='container'> <div className='container'>
{/* MANTIDO: Título limpo */} <h3 className='mb-4'>Editar Relatório do Paciente: {patient?.full_name || '...'}</h3>
<h3 className='mb-4'>Editar Relatório do Paciente: {patientData?.full_name}</h3>
{/* MUDANÇA: Removidos todos os inputs de texto avulsos */}
{/* Campo do Tiptap Editor */}
<div className='mb-3'> <div className='mb-3'>
{/* MUDANÇA: Título ajustado */}
<h5 className='mb-2'>Conteúdo do Relatório</h5> <h5 className='mb-2'>Conteúdo do Relatório</h5>
<TiptapEditor <TiptapEditor content={html} onChange={(newHtml) => setHtml(newHtml)} />
content={relatorioData.content}
onChange={handleEditorChange}
/>
</div> </div>
<button className='btn btn-success' onClick={handleSave}>
Salvar Relatório <div className='d-flex justify-content-center mt-4'>
<button className='btn btn-success' onClick={handleSave} disabled={loading}>
{loading ? 'Salvando...' : 'Salvar Relatório'}
</button> </button>
</div> </div>
) </div>
} );
};
export default EditPageRelatorio;
export default EditPageRelatorio

View File

@ -1,44 +1,243 @@
import React, { useEffect, useState, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import API_KEY from '../components/utils/apiKeys';
import { useAuth } from '../components/utils/AuthProvider';
import TiptapEditor from './TiptapEditor';
import { GetAllPatients, GetPatientByID } from '../components/utils/Functions-Endpoints/Patient';
import { GetAllDoctors, GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor';
import './styleMedico/FormNovoRelatorio.css';
import '../PagesMedico/styleMedico/FormNovoRelatorio.css'
import API_KEY from '../components/utils/apiKeys'
import FormRelatorio from '../components/FormRelatorio'
import { useState } from 'react'
import { useAuth } from '../components/utils/AuthProvider'
const FormNovoRelatorio = () => { const FormNovoRelatorio = () => {
const [DictInfo, setDictInfo] = useState({}) const { getAuthorizationHeader } = useAuth();
const authHeader = getAuthorizationHeader();
const navigate = useNavigate();
const {getAuthorizationHeader} = useAuth() const [patients, setPatients] = useState([]);
let authHeader = getAuthorizationHeader() const [doctors, setDoctors] = useState([]);
const [loadingPatients, setLoadingPatients] = useState(true);
const [loadingDoctors, setLoadingDoctors] = useState(true);
const handleSave = (data) => { // formulário
console.log("Relatório salvo:", data); const [form, setForm] = useState({
patient_id: '',
patient_name: '',
patient_birth: '',
doctor_id: '',
doctor_name: '',
contentHtml: '',
});
var myHeaders = new Headers(); // campos de busca (texto)
myHeaders.append("apikey", API_KEY); const [patientQuery, setPatientQuery] = useState('');
myHeaders.append("Authorization", authHeader); const [doctorQuery, setDoctorQuery] = useState('');
myHeaders.append("Content-Type", "application/json");
var raw = JSON.stringify({...data}); // dropdown control
const [showPatientDropdown, setShowPatientDropdown] = useState(false);
const [showDoctorDropdown, setShowDoctorDropdown] = useState(false);
var requestOptions = { const patientRef = useRef();
const doctorRef = useRef();
useEffect(() => {
// carregar pacientes
let mounted = true;
const loadPatients = async () => {
setLoadingPatients(true);
try {
const list = await GetAllPatients(authHeader);
if (mounted && Array.isArray(list)) setPatients(list);
} catch (err) {
console.error('Erro GetAllPatients:', err);
} finally {
if (mounted) setLoadingPatients(false);
}
};
const loadDoctors = async () => {
setLoadingDoctors(true);
try {
const list = await GetAllDoctors(authHeader);
if (mounted && Array.isArray(list)) setDoctors(list);
} catch (err) {
console.error('Erro GetAllDoctors:', err);
} finally {
if (mounted) setLoadingDoctors(false);
}
};
loadPatients();
loadDoctors();
return () => { mounted = false; };
}, [authHeader]);
// fechar dropdowns quando clicar fora
useEffect(() => {
const handleClick = (e) => {
if (patientRef.current && !patientRef.current.contains(e.target)) setShowPatientDropdown(false);
if (doctorRef.current && !doctorRef.current.contains(e.target)) setShowDoctorDropdown(false);
};
document.addEventListener('click', handleClick);
return () => document.removeEventListener('click', handleClick);
}, []);
const generateTemplate = (patientName = '', birthDate = '', doctorName = '') => {
return `
<div>
<p style="text-align:center; font-weight:bold;">Clinica Rise up</p>
<p style="text-align:center;">Dr - CRM/SP 123456</p>
<p style="text-align:center;">Avenida - (79) 9 4444-4444</p>
<br/>
<p><strong>Paciente:</strong> ${patientName}</p>
<p><strong>Data de nascimento:</strong> ${birthDate}</p>
<p><strong>Data do exame:</strong> </p>
<p><strong>Exame:</strong> </p>
<br/>
<p><strong>Diagnóstico:</strong></p>
<p></p>
<br/>
<p><strong>Conclusão:</strong></p>
<p></p>
<br/>
<p>Dr ${doctorName}</p>
<p>Emitido em: 0</p>
</div>
`;
};
// escolher paciente (clicando na lista)
const choosePatient = async (patient) => {
setForm(prev => ({
...prev,
patient_id: patient.id,
patient_name: patient.full_name || '',
patient_birth: patient.birth_date || '',
contentHtml: generateTemplate(patient.full_name || '', patient.birth_date || '', form.doctor_name)
}));
setPatientQuery('');
setShowPatientDropdown(false);
};
const chooseDoctor = (doctor) => {
setForm(prev => ({
...prev,
doctor_id: doctor.id,
doctor_name: doctor.full_name || '',
contentHtml: generateTemplate(form.patient_name, form.patient_birth, doctor.full_name || '')
}));
setDoctorQuery('');
setShowDoctorDropdown(false);
};
// filtrar pela query (startsWith)
const filteredPatients = patientQuery
? patients.filter(p => (p.full_name || '').toLowerCase().startsWith(patientQuery.toLowerCase())).slice(0, 40)
: [];
const filteredDoctors = doctorQuery
? doctors.filter(d => (d.full_name || '').toLowerCase().startsWith(doctorQuery.toLowerCase())).slice(0, 40)
: [];
const handleEditorChange = (html) => setForm(prev => ({ ...prev, contentHtml: html }));
// salvar novo relatório
const handleSubmit = async (e) => {
e.preventDefault();
if (!form.patient_id) return alert('Selecione o paciente (clicando no item) antes de salvar.');
if (!form.doctor_id) return alert('Selecione o médico (clicando no item) antes de salvar.');
try {
const myHeaders = new Headers();
myHeaders.append('apikey', API_KEY);
myHeaders.append('Authorization', authHeader);
myHeaders.append('Content-Type', 'application/json');
const body = JSON.stringify({
patient_id: form.patient_id,
content: form.contentHtml,
content_html: form.contentHtml,
requested_by: form.doctor_name || '',
created_by: form.doctor_id || null,
status: 'draft'
});
const res = await fetch('https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports', {
method: 'POST', method: 'POST',
headers: myHeaders, headers: myHeaders,
body: raw, body,
redirect: 'follow' });
};
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports", requestOptions) if (!res.ok) {
.then(response => response.text()) const txt = await res.text();
.then(result => console.log(result)) console.error('Erro POST criar relatório:', res.status, txt);
.catch(error => console.log('error', error)); // mostra mensagem mais útil
return alert(`Erro ao criar relatório (ver console). Status ${res.status}`);
} }
return ( alert('Relatório criado com sucesso!');
<div> navigate('/medico/relatorios');
<h3>Criar Novo Relatorio</h3> } catch (err) {
<FormRelatorio DictInfo={DictInfo} setDictInfo={setDictInfo} onSave={handleSave} /> console.error('Erro salvar relatório:', err);
</div> alert('Erro ao salvar relatório. Veja console.');
) }
} };
export default FormNovoRelatorio return (
<div className="container">
<h3 className="mb-4">Criar Novo Relatório</h3>
<form onSubmit={handleSubmit} className="card p-4 mb-4">
<div className="row g-3 align-items-end">
<div className="col-md-6" ref={patientRef}>
<label className="form-label">Buscar paciente (digite para filtrar)</label>
<input
className="form-control"
placeholder="Comece a digitar (ex.: m para pacientes que começam com m)"
value={patientQuery}
onChange={(e) => { setPatientQuery(e.target.value); setShowPatientDropdown(true); }}
onFocus={() => setShowPatientDropdown(true)}
/>
{showPatientDropdown && patientQuery && (
<ul className="list-group position-absolute" style={{ zIndex: 50, maxHeight: 220, overflowY: 'auto', width: '45%' }}>
{filteredPatients.length > 0 ? filteredPatients.map(p => (
<li key={p.id} className="list-group-item list-group-item-action" onClick={() => choosePatient(p)}>
{p.full_name} {p.cpf ? `- ${p.cpf}` : ''}
</li>
)) : <li className="list-group-item">Nenhum paciente começando com "{patientQuery}"</li>}
</ul>
)}
<div className="form-text">Clique no paciente desejado para selecioná-lo e preencher o template.</div>
</div>
<div className="col-md-6" ref={doctorRef}>
<label className="form-label">Buscar médico (digite para filtrar)</label>
<input
className="form-control"
placeholder="Comece a digitar o nome do médico"
value={doctorQuery}
onChange={(e) => { setDoctorQuery(e.target.value); setShowDoctorDropdown(true); }}
onFocus={() => setShowDoctorDropdown(true)}
/>
{showDoctorDropdown && doctorQuery && (
<ul className="list-group position-absolute" style={{ zIndex: 50, maxHeight: 220, overflowY: 'auto', width: '45%', right: 0 }}>
{filteredDoctors.length > 0 ? filteredDoctors.map(d => (
<li key={d.id} className="list-group-item list-group-item-action" onClick={() => chooseDoctor(d)}>
{d.full_name} {d.crm ? `- CRM ${d.crm}` : ''}
</li>
)) : <li className="list-group-item">Nenhum médico começando com "{doctorQuery}"</li>}
</ul>
)}
<div className="form-text">Clique no médico desejado para selecioná-lo.</div>
</div>
<div className="col-12 text-end">
<button type="submit" className="btn btn-success">Salvar Relatório</button>
</div>
</div>
<hr className="my-3" />
<h5>Conteúdo do Relatório (edite tudo aqui)</h5>
<TiptapEditor content={form.contentHtml || generateTemplate(form.patient_name, form.patient_birth, form.doctor_name)} onChange={handleEditorChange} />
</form>
</div>
);
};
export default FormNovoRelatorio;

View File

@ -1,74 +1,55 @@
import React from 'react'; import React, { useEffect } from 'react';
import { useEditor, EditorContent } from '@tiptap/react'; import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit'; import StarterKit from '@tiptap/starter-kit';
import Link from '@tiptap/extension-link'; import Link from '@tiptap/extension-link';
// Componente da barra de menu (Menu Bar)
// MenuBar simples
const MenuBar = ({ editor }) => { const MenuBar = ({ editor }) => {
if (!editor) { if (!editor) return null;
return null; const btn = { marginRight: '6px', padding: '4px 8px', cursor: 'pointer', border: '1px solid #ccc', borderRadius: 4 };
}
// 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 ( return (
<div style={{ padding: '8px', borderBottom: '1px solid #ccc', display: 'flex', flexWrap: 'wrap' }}> <div style={{ padding: 8, borderBottom: '1px solid #e6e6e6', display: 'flex', flexWrap: 'wrap' }}>
<button <button style={{ ...btn, fontWeight: 'bold' }} onClick={() => editor.chain().focus().toggleBold().run()}>B</button>
onClick={() => editor.chain().focus().toggleBold().run()} <button style={{ ...btn }} onClick={() => editor.chain().focus().toggleItalic().run()}>I</button>
disabled={!editor.can().chain().focus().toggleBold().run()} <button style={{ ...btn }} onClick={() => editor.chain().focus().toggleBulletList().run()}>Lista</button>
style={{ ...buttonStyle, fontWeight: 'bold' }} <button style={{ ...btn }} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}>Título 2</button>
> <button style={{ ...btn }} onClick={() => { const url = prompt('URL'); if (url) editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run(); }}>Link</button>
B
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
disabled={!editor.can().chain().focus().toggleItalic().run()}
style={{ ...buttonStyle, fontStyle: 'italic' }}
>
I
</button>
<button
onClick={() => editor.chain().focus().toggleBulletList().run()}
style={{ ...buttonStyle, backgroundColor: editor.isActive('bulletList') ? '#ddd' : 'white' }}
>
Lista
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
style={{ ...buttonStyle, backgroundColor: editor.isActive('heading', { level: 2 }) ? '#ddd' : 'white' }}
>
Título 2
</button>
{/* Adicione mais botões conforme a necessidade (link, código, etc.) */}
</div> </div>
); );
}; };
// Componente principal do Editor
const TiptapEditor = ({ content, onChange }) => { const TiptapEditor = ({ content, onChange }) => {
const editor = useEditor({ const editor = useEditor({
extensions: [ extensions: [
StarterKit.configure({ StarterKit.configure({ hardBreak: false }),
// Desativa 'hardBreak' e 'blockquote' se não forem necessários para simplificar Link,
hardBreak: false,
}),
Link, // Adiciona suporte para links
], ],
content: content || '<p>Inicie o relatório aqui...</p>', content: content || '<p>Inicie o relatório aqui...</p>',
onUpdate: ({ editor }) => { onUpdate: ({ editor }) => {
// Quando o conteúdo muda, chama a função onChange com o HTML onChange && onChange(editor.getHTML());
onChange(editor.getHTML());
}, },
}); });
// Se o pai mudar 'content', atualizamos o editor
useEffect(() => {
if (!editor) return;
// Só setContent se for diferente para evitar perda de edição
try {
const current = editor.getHTML();
if ((content || '').trim() && content !== current) {
editor.commands.setContent(content);
}
} catch (e) {
// ignore
}
}, [editor, content]);
return ( return (
<div className='tiptap-editor-container' style={{ border: '1px solid #ccc', borderRadius: '4px' }}> <div className='tiptap-editor-container' style={{ border: '1px solid #ddd', borderRadius: 6, overflow: 'hidden' }}>
<MenuBar editor={editor} /> <MenuBar editor={editor} />
<EditorContent editor={editor} style={{ minHeight: '300px', padding: '10px' }} /> <EditorContent editor={editor} style={{ minHeight: 360, padding: 12, background: 'white' }} />
</div> </div>
); );
}; };
export default TiptapEditor; export default TiptapEditor;

View File

@ -3,15 +3,18 @@ import { useEffect, useMemo,useState } from 'react'
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor' import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor'
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient' import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient'
import { useAuth } from '../components/utils/AuthProvider' import { useAuth } from '../components/utils/AuthProvider'
import { useNavigate } from 'react-router-dom'
const CardConsultaPaciente = ({consulta, setConsulta, setSelectedId, setShowDeleteModal}) => {
const CardConsultaPaciente = ({consulta}) => { const navigate = useNavigate()
const [Paciente, setPaciente] = useState({}) const [Paciente, setPaciente] = useState({})
const [Medico, setMedico] = useState({}) const [Medico, setMedico] = useState({})
const {getAuthorizationHeader} = useAuth() const {getAuthorizationHeader} = useAuth()
const authHeader = getAuthorizationHeader() const authHeader = getAuthorizationHeader()
const ids = useMemo(() => { const ids = useMemo(() => {
return { return {
doctor_id: consulta?.doctor_id, doctor_id: consulta?.doctor_id,
@ -50,6 +53,8 @@ const CardConsultaPaciente = ({consulta}) => {
console.log(horario) console.log(horario)
const deleteConsulta = () => {}
return ( return (
<div class="card-consulta"> <div class="card-consulta">
<div class="horario-container"> <div class="horario-container">
@ -59,10 +64,40 @@ const CardConsultaPaciente = ({consulta}) => {
</span> </span>
</div> </div>
<div class="info-container"> <div class="info-container">
<span class="informacao">
<p>{`Inicio: ${horario.split(":")[0]}:${horario.split(":")[1]}`}</p>
<p class="informacao">
Dr {Medico?.full_name} - {Medico?.specialty} Dr {Medico?.full_name} - {Medico?.specialty}
</span> </p>
<div className='actions-container'>
<button className="btn btn-sm btn-edit-custom"
onClick={() => {navigate(`edit`)
setConsulta({...consulta,paciente_cpf:Paciente.cpf, paciente_nome:Paciente.full_name, nome_medico:Medico.full_name})
}}
>
<i className="bi bi-pencil me-1"></i>
</button>
<button
className="btn btn-sm btn-delete-custom-style "
onClick={() => {
console.log(consulta.id)
setSelectedId(consulta.id)
setShowDeleteModal(true);
}}
>
<i className="bi bi-trash me-1"></i>
</button>
</div> </div>
</div>
</div> </div>
) )
} }

View File

@ -0,0 +1,112 @@
import React from 'react'
import { useAuth } from '../components/utils/AuthProvider'
import { useState, useEffect } from 'react'
import API_KEY from '../components/utils/apiKeys'
import { UserInfos } from '../components/utils/Functions-Endpoints/General'
import FormConsultaPaciente from './FormConsultaPaciente'
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor'
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient'
const ConsultaEditPage = ({dadosConsulta}) => {
console.log(dadosConsulta, "editar")
const {getAuthorizationHeader} = useAuth()
const [idUsuario, setIDusuario] = useState("6e7f8829-0574-42df-9290-8dbb70f75ada")
const [DictInfo, setDict] = useState({})
const [Medico, setMedico] = useState({})
const [Paciente, setPaciente] = useState([])
useEffect(() => {
setDict({...dadosConsulta})
const fetchMedicoePaciente = async () => {
console.log(dadosConsulta.doctor_id)
let Medico = await GetDoctorByID(dadosConsulta.doctor_id,authHeader )
let Paciente = await GetPatientByID(dadosConsulta.patient_id,authHeader )
console.log(Paciente, 'Paciente')
setMedico(Medico[0])
setPaciente(Paciente[0])
}
const ColherInfoUsuario =async () => {
const result = await UserInfos(authHeader)
setIDusuario(result?.profile?.id)
}
ColherInfoUsuario()
fetchMedicoePaciente()
}, [])
useEffect(() => {
setDict({...DictInfo, medico_nome:Medico?.full_name, dataAtendimento:dadosConsulta.scheduled_at?.split("T")[0]})
}, [Medico])
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,
"duration_minutes": 30,
"chief_complaint": "Dor de cabeça há 3 ",
"created_by": idUsuario,
"scheduled_at": `${DictParaPatch.dataAtendimento}T${DictParaPatch.horarioInicio}:00.000Z`,
"appointment_type": DictParaPatch.tipo_consulta,
"patient_notes": "Prefiro horário pela manhã",
"insurance_provider": DictParaPatch.convenio,
"status": DictParaPatch.status,
"created_by": idUsuario
});
var requestOptions = {
method: 'PATCH',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${DictInfo.id}`, requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
}
return (
<div>
<FormConsultaPaciente agendamento={DictInfo} setAgendamento={setDict} onSave={handleSave}/>
</div>
)
}
export default ConsultaEditPage

View File

@ -6,9 +6,12 @@ import { useEffect, useState } from 'react'
import API_KEY from '../components/utils/apiKeys' import API_KEY from '../components/utils/apiKeys'
import { useAuth } from '../components/utils/AuthProvider' import { useAuth } from '../components/utils/AuthProvider'
const ConsultasPaciente = () => { const ConsultasPaciente = ({setConsulta}) => {
const {getAuthorizationHeader} = useAuth() const {getAuthorizationHeader} = useAuth()
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [selectedID, setSelectedId] = useState("")
let authHeader = getAuthorizationHeader() let authHeader = getAuthorizationHeader()
const [consultas, setConsultas] = useState([]) const [consultas, setConsultas] = useState([])
@ -56,16 +59,32 @@ const FiltrarAgendamentos = (agendamentos, id) => {
}, []) }, [])
const navigate = useNavigate() const navigate = useNavigate()
/*
const consultas = [ const deleteConsulta= (ID) => {
{ var myHeaders = new Headers();
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", myHeaders.append("Content-Type", "application/json");
"doctor_id": "eaca4372-17bc-4905-9eff-7aeda46157b4", myHeaders.append('apikey', API_KEY)
"patient_id": "3854866a-5476-48be-8313-77029ccdd7a7", myHeaders.append("authorization", authHeader)
"scheduled_at": "2019-08-24T14:15:22Z",
"status": "string"
var raw = JSON.stringify({ "status":"cancelled"
});
var requestOptions = {
method: 'PATCH',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${selectedID}`, requestOptions)
.then(response => {if(response.status !== 200)(console.log(response))})
.then(result => console.log(result))
.catch(error => console.log('error', error));
console.log("deletar", ID)
} }
]*/
return ( return (
<div> <div>
@ -81,12 +100,52 @@ const FiltrarAgendamentos = (agendamentos, id) => {
<h2>Seus proximos atendimentos</h2> <h2>Seus proximos atendimentos</h2>
{consultas.map((consulta) => ( {consultas.map((consulta) => (
<CardConsultaPaciente consulta={consulta}/> <CardConsultaPaciente consulta={consulta} setConsulta={setConsulta} setShowDeleteModal={setShowDeleteModal} setSelectedId={ setSelectedId}/>
))} ))}
{showDeleteModal &&
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-header bg-danger bg-opacity-25">
<h5 className="modal-title text-danger">
Confirmação de Exclusão
</h5>
<button
type="button"
className="btn-close"
onClick={() => setShowDeleteModal(false)}
></button>
</div>
<div className="modal-body">
<p className="mb-0 fs-5">
Tem certeza que deseja excluir este agendamento?
</p>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-primary"
onClick={() => setShowDeleteModal(false)}
>
Cancelar
</button>
<h2>Historico de consultas:</h2> <button
type="button"
className="btn btn-danger"
onClick={() => {deleteConsulta(selectedID);setShowDeleteModal(false)}}
>
<i className="bi bi-trash me-1"></i> Excluir
</button>
</div>
</div>
</div>}
</div> </div>
</div> </div>

View File

@ -5,12 +5,13 @@ import { GetPatientByCPF } from "../components/utils/Functions-Endpoints/Patient
import { GetDoctorByName, GetAllDoctors } from "../components/utils/Functions-Endpoints/Doctor"; import { GetDoctorByName, GetAllDoctors } from "../components/utils/Functions-Endpoints/Doctor";
import { useAuth } from "../components/utils/AuthProvider"; import { useAuth } from "../components/utils/AuthProvider";
import API_KEY from "../components/utils/apiKeys"; import API_KEY from "../components/utils/apiKeys";
import { useNavigate } from "react-router-dom";
const FormConsultaPaciente = ({ onCancel, onSave, setAgendamento, agendamento }) => { const FormConsultaPaciente = ({ onCancel, onSave, setAgendamento, agendamento }) => {
const {getAuthorizationHeader} = useAuth() const {getAuthorizationHeader} = useAuth()
console.log(agendamento, 'aqui2') console.log(agendamento?.dataAtendimento, 'aqui2')
const navigate = useNavigate()
const [selectedFile, setSelectedFile] = useState(null); const [selectedFile, setSelectedFile] = useState(null);
const [anexos, setAnexos] = useState([]); const [anexos, setAnexos] = useState([]);
const [loadingAnexos, setLoadingAnexos] = useState(false); const [loadingAnexos, setLoadingAnexos] = useState(false);
@ -168,6 +169,7 @@ const formatarHora = (datetimeString) => {
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
alert("Agendamento salvo!"); alert("Agendamento salvo!");
navigate("/paciente/agendamento")
onSave({...agendamento, horarioInicio:horarioInicio}) onSave({...agendamento, horarioInicio:horarioInicio})
}; };

View File

@ -6,7 +6,7 @@
border-radius: 10px; /* Cantos arredondados */ border-radius: 10px; /* Cantos arredondados */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Sombra suave */ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Sombra suave */
overflow: hidden; /* Garante que o fundo azul não 'vaze' */ overflow: hidden; /* Garante que o fundo azul não 'vaze' */
width: 280px; /* Largura de exemplo */ /* width: 280px; /* Largura de exemplo */
margin: 20px; margin: 20px;
font-family: Arial, sans-serif; /* Fonte legível */ font-family: Arial, sans-serif; /* Fonte legível */
} }
@ -37,8 +37,70 @@
display: flex; display: flex;
align-items: center; align-items: center;
flex-grow: 1; /* Faz com que a div de informações preencha o espaço restante */ flex-grow: 1; /* Faz com que a div de informações preencha o espaço restante */
gap:6rem;
} }
.informacao { .informacao {
font-size: 1.1em; font-size: 1.1em;
} }
.actions-container {
margin: auto;
display: flex;
gap: 8px;
padding: 8px;
border-radius: 10px;
margin-left: 2rem;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease-in-out;
/* 🎨 Glassmorphism */
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(80px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
/* Mostra no hover do card */
.card-consulta:hover .actions-container {
opacity: 1;
visibility: visible;
transform: translateY(-2px);
}
/* 3. Estilos base para o botão de edição (amarelo) */
.btn-edit-custom-style {
background-color: #ffc107; /* Amarelo da sua imagem */
color: #343a40; /* Cor do ícone (cinza escuro para contraste) */
border: none;
padding: 8px 12px; /* Ajuste o padding para o tamanho do botão */
border-radius: 0.25rem; /* Leve arredondamento de borda */
transition: background-color 0.2s ease-in-out; /* Suaviza a transição de cor */
}
/* 5. Estilos base para o botão de exclusão (vermelho) */
.btn-delete-custom-style {
background-color: #dc3545; /* Vermelho da sua imagem */
color: #ffffff; /* Cor do ícone (branco para contraste) */
border: none;
padding: 8px 12px; /* Ajuste o padding para o tamanho do botão */
border-radius: 0.25rem; /* Leve arredondamento de borda */
transition: background-color 0.2s ease-in-out; /* Suaviza a transição de cor */
font-weight:bold ;
}
/* 6. Estilo de hover para o botão de exclusão */
.btn-delete-custom-style:hover {
background-color: #c82333; /* Um vermelho um pouco mais escuro para o hover */
filter: brightness(90%); /* Alternativa: escurecer um pouco mais */
}

View File

@ -7,8 +7,6 @@ import "./style/card-consulta.css"
const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, setDictInfo, setSelectedId} ) => { const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, setDictInfo, setSelectedId} ) => {
const navigate = useNavigate(); const navigate = useNavigate();
console.log(DadosConsulta, "AQUIIII")
const {getAuthorizationHeader} = useAuth() const {getAuthorizationHeader} = useAuth()
const authHeader = getAuthorizationHeader() const authHeader = getAuthorizationHeader()
const [Paciente, setPaciente] = useState() const [Paciente, setPaciente] = useState()

View File

@ -11,6 +11,9 @@ const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) =>
console.log(agendamento, 'aqui2') console.log(agendamento, 'aqui2')
const [sessoes,setSessoes] = useState(1)
const [tempoBaseConsulta, setTempoBaseConsulta] = useState(30); // NOVO: Tempo base da consulta em minutos
const [selectedFile, setSelectedFile] = useState(null); const [selectedFile, setSelectedFile] = useState(null);
const [anexos, setAnexos] = useState([]); const [anexos, setAnexos] = useState([]);
const [loadingAnexos, setLoadingAnexos] = useState(false); const [loadingAnexos, setLoadingAnexos] = useState(false);
@ -165,6 +168,33 @@ const formatarHora = (datetimeString) => {
disabled: !item.available disabled: !item.available
})); }));
const calcularHorarioTermino = (inicio, sessoes, tempoBase) => {
if (!inicio || inicio.length !== 5 || !inicio.includes(':')) return '';
const [horas, minutos] = inicio.split(':').map(Number);
const minutosInicio = (horas * 60) + minutos;
const duracaoTotalMinutos = sessoes * tempoBase;
const minutosTermino = minutosInicio + duracaoTotalMinutos;
const horaTermino = Math.floor(minutosTermino / 60) % 24;
const minutoTermino = minutosTermino % 60;
const formatar = (num) => String(num).padStart(2, '0');
return `${formatar(horaTermino)}:${formatar(minutoTermino)}`;
};
useEffect(() => {
// Recalcula o horário de término sempre que houver alteração nas variáveis dependentes
const novoTermino = calcularHorarioTermino(horarioInicio, sessoes, tempoBaseConsulta);
setHorarioTermino(novoTermino);
setAgendamento(prev => ({
...prev,
horarioTermino: novoTermino
}));
}, [horarioInicio, sessoes, tempoBaseConsulta, setAgendamento]);
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
alert("Agendamento salvo!"); alert("Agendamento salvo!");
@ -247,27 +277,12 @@ const handleSubmit = (e) => {
</select> </select>
</div> </div>
<div className="form-check ">
<input className="form-check-input checkbox-cstom" type="checkbox" name="status" onChange={handleChange} />
<label className="form-check-label checkbox-label" htmlFor="vip">
Fila de espera
</label>
</div>
</div> </div>
<section id="informacoes-atendimento-segunda-linha"> <section id="informacoes-atendimento-segunda-linha">
<section id="informacoes-atendimento-segunda-linha-esquerda"> <section id="informacoes-atendimento-segunda-linha-esquerda">
<div className="campo-informacoes-atendimento"> <div className="campo-informacoes-atendimento">
<div className='campo-de-input'>
<label>Unidade *</label>
<select name="unidade">
<option value="" disabled invisible selected>Selecione a unidade</option>
<option value="centro">Núcleo de Especialidades Integradas</option>
<option value="leste">Unidade Leste</option>
</select>
</div>
<div className="campo-de-input"> <div className="campo-de-input">
@ -275,9 +290,8 @@ const handleSubmit = (e) => {
<input type="date" name="dataAtendimento" onChange={handleChange} required /> <input type="date" name="dataAtendimento" onChange={handleChange} required />
</div> </div>
</div> </div>
<div className="linha">
{/* Dropdown de Início (Não modificado) */}
<div className="row">
<div className="campo-de-input"> <div className="campo-de-input">
<label htmlFor="inicio">Início *</label> <label htmlFor="inicio">Início *</label>
<select <select
@ -301,38 +315,44 @@ const handleSubmit = (e) => {
</select> </select>
</div> </div>
<div> {/* SELETOR DE SESSÕES MODIFICADO */}
{/* Removemos o 'label' para evitar o desalinhamento e colocamos o texto acima */}
<div className='seletor-wrapper'>
<label>Número de Sessões *</label> {/* Novo label para o seletor */}
<div className='sessao-contador'>
<button
type="button" /* Adicionado para evitar submissão de formulário */
onClick={() => {if(sessoes === 0)return; else(setSessoes(sessoes - 1))}}
disabled={sessoes === 0} /* Desabilita o botão no limite */
>
<i className="bi bi-chevron-compact-left"></i>
</button>
<p className='sessao-valor'>{sessoes}</p> {/* Adicionada classe para estilização */}
<button
type="button" /* Adicionado para evitar submissão de formulário */
onClick={() => {if(sessoes === 3 )return; else(setSessoes(sessoes + 1))}}
disabled={sessoes === 3} /* Desabilita o botão no limite */
>
<i className="bi bi-chevron-compact-right"></i>
</button>
</div>
</div> </div>
{/* Dropdown de Término */}
<div className="campo-de-input"> <div className="campo-de-input">
<label htmlFor="termino">Término *</label> <label htmlFor="termino">Término *</label>
<select <input
type="text"
id="termino" id="termino"
name="termino" name="termino"
required value={horarioTermino || '— —'}
value={horarioTermino} readOnly
onChange={(e) => setHorarioTermino(e.target.value)} className="horario-termino-readonly"
> />
<option value="" disabled>Selecione a hora de término</option>
{opcoesDeHorario?.map((opcao, index) => (
<option
key={index}
value={opcao.value}
disabled={opcao.disabled}
>
{opcao.label}
{opcao.disabled && " (Indisponível)"}
</option>
))}
</select>
</div>
</div>
</div>
</div>
</section> </section>
@ -344,13 +364,21 @@ const handleSubmit = (e) => {
<textarea name="observacoes" rows="4" cols="1"></textarea> <textarea name="observacoes" rows="4" cols="1"></textarea>
</div> </div>
</section> </section>
</section> </section>
<div className="form-actions"> <div className="form-actions">
<button type="submit" className="btn-primary">Salvar agendamento</button> <button type="submit" className="btn-primary">Salvar agendamento</button>
<button type="button" className="btn-cancel" onClick={onCancel}>Cancelar</button> <button type="button" className="btn-cancel" onClick={onCancel}>Cancelar</button>
</div> </div>
</form> </form>
<div className="campo-de-input-check">
<input className="form-check-input form-custom-check" type="checkbox" name="status" onChange={handleChange} />
<label className="form-check-label checkbox-label" htmlFor="status">
Adicionar a fila de espera
</label>
</div>
</div> </div>
); );

View File

@ -19,6 +19,28 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
setDia(ListaDiasComAgendamentos[indiceAcesso]) setDia(ListaDiasComAgendamentos[indiceAcesso])
}, [indiceAcesso]) }, [indiceAcesso])
const formatarDataComDia = (dataISO) => {
if (!dataISO) return '';
const data = new Date(dataISO); // converte para objeto Date
// nomes dos dias da semana
const dias = [
'Segunda-feira',
'Terça-feira',
'Quarta-feira',
'Quinta-feira',
'Sexta-feira',
'Sábado'
];
const diaSemana = dias[data.getDay()]; // 0 = Domingo, 1 = Segunda, etc.
const dia = dataISO.split('-')[2];
const mes = dataISO.split('-')[1];
const ano = dataISO.split('-')[0];
return `${diaSemana}, ${dia}/${mes}/${ano}`;
};
return ( return (
<div> <div>
@ -27,7 +49,8 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
<button onClick={() => {if(indiceAcesso === 0)return; else(setIndiceAcesso(indiceAcesso - 1))}}> <i className="bi bi-chevron-compact-left"></i></button> <button onClick={() => {if(indiceAcesso === 0)return; else(setIndiceAcesso(indiceAcesso - 1))}}> <i className="bi bi-chevron-compact-left"></i></button>
<p>{Dia ? `${Dia?.split('-')[2]}/${Dia?.split('-')[1]}/${Dia?.split('-')[0]}`: ''}</p> <p>{Dia ? formatarDataComDia(Dia) : ''}</p>
<button onClick={() => {if(ListaDiasComAgendamentos.length - 1 === indiceAcesso)return; else(setIndiceAcesso(indiceAcesso + 1))}}> <i className="bi bi-chevron-compact-right"></i></button> <button onClick={() => {if(ListaDiasComAgendamentos.length - 1 === indiceAcesso)return; else(setIndiceAcesso(indiceAcesso + 1))}}> <i className="bi bi-chevron-compact-right"></i></button>
</div> </div>
</div> </div>

View File

@ -67,15 +67,16 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteM
break break
} }
} }
console.log(semanas)
return semanas return semanas
}, [agendamentos, AnoAtual]) // Adicionei AnoAtual como dependência por segurança }, [agendamentos, AnoAtual])
// --- EFEITO PARA POPULAR O ESTADO --- // --- EFEITO PARA POPULAR O ESTADO ---
useEffect(() => { useEffect(() => {
setSemanasOrganizadas(OrganizarAgendamentosSemanais); setSemanasOrganizadas(OrganizarAgendamentosSemanais);
// NOTA: Ao carregar, o Indice é 0, que é a primeira semana.
//console.log(semanasOrganizadas, `aqui`)
}, [OrganizarAgendamentosSemanais]) }, [OrganizarAgendamentosSemanais])
// --- NOVAS FUNÇÕES DE NAVEGAÇÃO --- // --- NOVAS FUNÇÕES DE NAVEGAÇÃO ---
@ -156,15 +157,28 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteM
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{indicesDeLinha.map((indiceLinha) => ( {indicesDeLinha.map((indiceLinha) => {
//let schedulet_at = semanaParaRenderizar.segunda[indiceLinha].scheduled_at.split("T")
// let horario = schedulet_at[1].split(":")
console.log(semanaParaRenderizar, "aqui")
return(
<tr key={indiceLinha}> <tr key={indiceLinha}>
{/* Célula para Horário (Pode ser ajustado para mostrar o horário real) */}
<td></td> <td>
{/* <p className='horario-texto'> {`${horario[0]}:${horario[1]}`} </p>*/}
</td>
{/* Mapeamento de COLUNAS (dias) */} {/* Mapeamento de COLUNAS (dias) */}
<td> <td>
{semanaParaRenderizar.segunda[indiceLinha] {semanaParaRenderizar?.segunda[indiceLinha]
? <CardConsulta DadosConsulta={semanaParaRenderizar.segunda[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} /> ? <CardConsulta DadosConsulta={semanaParaRenderizar?.segunda[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />
: null : null
} }
</td> </td>
@ -193,7 +207,7 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteM
} }
</td> </td>
</tr> </tr>
))} )})}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -110,6 +110,7 @@ svg{
margin-top: 25px; margin-top: 25px;
display: flex; display: flex;
gap: 12px; gap: 12px;
justify-content: flex-end;
} }
.btn-primary { .btn-primary {
@ -390,3 +391,84 @@ html[data-bs-theme="dark"] svg {
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
/* Container dos três elementos na linha */
.linha {
display: flex;
align-items: flex-end; /* Garante que os campos de input e o seletor fiquem alinhados pela base */
gap: 20px; /* Espaçamento entre os campos */
}
/* ------------------------------------------- */
/* ESTILIZAÇÃO DO SELETOR DE SESSÕES */
/* ------------------------------------------- */
.seletor-wrapper {
/* Garante que o label e o contador fiquem alinhados verticalmente com os selects */
display: flex;
flex-direction: column;
}
.sessao-contador {
/* Estilo de "campo de input" */
display: flex;
align-items: center;
justify-content: space-between;
/* Cores e Bordas */
background-color: #e9ecef; /* Cor cinza claro dos inputs do Bootstrap */
border: 1px solid #ced4da; /* Borda sutil */
border-radius: 0.25rem; /* Bordas arredondadas (Padrão Bootstrap) */
/* Garante a mesma altura dos selects */
height: 40px; /* Ajuste este valor para corresponder à altura exata do seu select */
width: 100px; /* Largura ajustável */
padding: 0 5px; /* Padding interno */
font-size: 1rem;
font-weight: 500;
}
.sessao-valor {
/* Estilo do número de sessões */
margin: 0;
padding: 0 5px;
font-size: 1.1rem; /* Um pouco maior que o texto dos selects */
color: #007bff; /* Cor azul destacada (como na sua imagem) */
}
.sessao-contador button {
/* Estilo dos botões de chevron */
background: none;
border: none;
cursor: pointer;
padding: 0 2px;
color: #495057; /* Cor do ícone */
font-size: 1.5rem; /* Aumenta o tamanho dos ícones do chevron */
line-height: 1; /* Alinha o ícone verticalmente */
transition: color 0.2s;
}
.sessao-contador button:hover:not(:disabled) {
color: #007bff; /* Cor azul ao passar o mouse */
}
.sessao-contador button:disabled {
cursor: not-allowed;
color: #adb5bd; /* Cor mais clara quando desabilitado */
}
/* ------------------------------------------- */
/* GARANTINDO COERÊNCIA NOS SELECTS */
/* ------------------------------------------- */
.campo-de-input select {
/* Se seus selects estiverem com estilos diferentes, este bloco garante que eles se pareçam */
/* com o seletor de sessões (se já usarem classes do Bootstrap, podem não precisar disso) */
background-color: #e9ecef; /* Fundo cinza claro */
border: 1px solid #ced4da; /* Borda sutil */
border-radius: 0.25rem;
height: 40px; /* Garante a mesma altura do sessao-contador */
/* Adicione mais estilos do seu input/select se necessário (ex: font-size, padding) */
}

View File

@ -1,8 +1,11 @@
import React, { useState, useRef } from "react"; import React, { useState, useRef, useCallback } from "react";
import { Link, useNavigate, useLocation } from "react-router-dom"; import { Link, useNavigate, useLocation } from "react-router-dom";
import "./DoctorForm.css"; import "./DoctorForm.css";
import HorariosDisponibilidade from "../doctors/HorariosDisponibilidade"; import HorariosDisponibilidade from "../doctors/HorariosDisponibilidade";
const ENDPOINT_AVAILABILITY =
"https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability";
function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
@ -123,9 +126,12 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
} }
}; };
const handleAvailabilityUpdate = (newAvailability) => { const handleAvailabilityUpdate = useCallback(
(newAvailability) => {
setFormData((prev) => ({ ...prev, availability: newAvailability })); setFormData((prev) => ({ ...prev, availability: newAvailability }));
}; },
[setFormData]
);
const handleCepBlur = async () => { const handleCepBlur = async () => {
const cep = formData.cep?.replace(/\D/g, ""); const cep = formData.cep?.replace(/\D/g, "");
@ -205,6 +211,42 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
} }
}, 300); }, 300);
}; };
const handleCreateAvailability = async (newAvailability) => {
try {
const response = await fetch(ENDPOINT_AVAILABILITY, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newAvailability),
});
const data = await response.json();
console.log("Disponibilidade criada :", data);
alert("Disponibilidade criada com sucesso!");
} catch (error) {
console.error("Erro ao criar disponibilidade:", error);
alert("Erro ao criar disponibilidade.");
}
};
const handlePatchAvailability = async (id, updatedAvailability) => {
try {
const response = await fetch(`${ENDPOINT_AVAILABILITY}?id=${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updatedAvailability),
});
const data = await response.json();
console.log("Disponibilidade atualizada:", data);
alert("Disponibilidade atualizada com sucesso!");
} catch (error) {
console.error("Erro ao atualizar disponibilidade:", error);
alert("Erro ao atualizar disponibilidade.");
}
};
const handleSubmit = async () => { const handleSubmit = async () => {
const missingFields = []; const missingFields = [];
@ -246,9 +288,23 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
try { try {
await onSave({ ...formData }); await onSave({ ...formData });
} catch (error) {
throw error; if (formData.availability && formData.availability.length > 0) {
if (formData.availabilityId) {
await handlePatchAvailability(
formData.availabilityId,
formData.availability
);
} else {
await handleCreateAvailability(formData.availability);
} }
}
alert("Médico salvo e disponibilidade enviada ao mock com sucesso!");
} catch (error) {
console.error("Erro ao salvar médico ou disponibilidade:", error);
alert("Erro ao salvar médico ou disponibilidade.");
};
}; };
const handleModalClose = () => { const handleModalClose = () => {

View File

@ -3,8 +3,8 @@ import { Clock } from "lucide-react";
const initialBlockTemplate = { const initialBlockTemplate = {
id: null, id: null,
inicio: "09:00", inicio: "07:00",
termino: "17:00", termino: "18:00",
isNew: true, isNew: true,
}; };
@ -118,9 +118,9 @@ const HorariosDisponibilidade = ({
flexDirection: window.innerWidth < 640 ? "column" : "row", flexDirection: window.innerWidth < 640 ? "column" : "row",
alignItems: window.innerWidth < 640 ? "flex-start" : "center", alignItems: window.innerWidth < 640 ? "flex-start" : "center",
justifyContent: "space-between", justifyContent: "space-between",
padding: "16px", padding: "8px",
marginBottom: "16px", marginBottom: "8px",
borderRadius: "12px", borderRadius: "8px",
boxShadow: boxShadow:
"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
transition: "all 0.3s", transition: "all 0.3s",
@ -132,7 +132,7 @@ const HorariosDisponibilidade = ({
style={{ style={{
display: "flex", display: "flex",
flexDirection: window.innerWidth < 640 ? "column" : "row", flexDirection: window.innerWidth < 640 ? "column" : "row",
gap: window.innerWidth < 640 ? "0" : "32px", gap: window.innerWidth < 640 ? "0" : "12px",
width: window.innerWidth < 640 ? "100%" : "auto", width: window.innerWidth < 640 ? "100%" : "auto",
}} }}
> >
@ -140,7 +140,7 @@ const HorariosDisponibilidade = ({
style={{ style={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "8px", gap: "4px",
marginBottom: window.innerWidth < 640 ? "8px" : "0", marginBottom: window.innerWidth < 640 ? "8px" : "0",
}} }}
> >
@ -159,20 +159,21 @@ const HorariosDisponibilidade = ({
handleTimeChange(dayIndex, bloco.id, "inicio", e.target.value) handleTimeChange(dayIndex, bloco.id, "inicio", e.target.value)
} }
style={{ style={{
padding: "8px", padding: "4px 6px",
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
borderRadius: "8px", borderRadius: "6px",
width: "100%", width: "100%",
boxSizing: "border-box", boxSizing: "border-box",
outline: "none", outline: "none",
fontSize: "13px"
}} }}
step="300" step="300"
/> />
<Clock <Clock
size={16} size={12}
style={{ style={{
position: "absolute", position: "absolute",
right: "12px", right: "8px",
top: "50%", top: "50%",
transform: "translateY(-50%)", transform: "translateY(-50%)",
color: "#9ca3af", color: "#9ca3af",
@ -182,10 +183,10 @@ const HorariosDisponibilidade = ({
</div> </div>
</div> </div>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}> <div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
<label <label
htmlFor={`termino-${dayIndex}-${bloco.id}`} htmlFor={`termino-${dayIndex}-${bloco.id}`}
style={{ fontWeight: 500, color: "#4b5563", width: "64px" }} style={{ fontWeight: 500, color: "#4b5563", width: "56px", fontSize: "13px" }}
> >
Término: Término:
</label> </label>
@ -198,20 +199,21 @@ const HorariosDisponibilidade = ({
handleTimeChange(dayIndex, bloco.id, "termino", e.target.value) handleTimeChange(dayIndex, bloco.id, "termino", e.target.value)
} }
style={{ style={{
padding: "8px", padding: "4px 6px",
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
borderRadius: "8px", borderRadius: "6px",
width: "100%", width: "100%",
boxSizing: "border-box", boxSizing: "border-box",
outline: "none", outline: "none",
fontSize: "13px",
}} }}
step="300" step="300"
/> />
<Clock <Clock
size={16} size={12}
style={{ style={{
position: "absolute", position: "absolute",
right: "12px", right: "8px",
top: "50%", top: "50%",
transform: "translateY(-50%)", transform: "translateY(-50%)",
color: "#9ca3af", color: "#9ca3af",
@ -225,12 +227,12 @@ const HorariosDisponibilidade = ({
<button <button
onClick={() => handleRemoveBlock(dayIndex, bloco.id)} onClick={() => handleRemoveBlock(dayIndex, bloco.id)}
style={{ style={{
marginTop: window.innerWidth < 640 ? "16px" : "0", marginTop: window.innerWidth < 640 ? "8px" : "0",
padding: "8px 24px", padding: "4px 10px",
backgroundColor: "#ef4444", backgroundColor: "#ef4444",
color: "white", color: "white",
fontWeight: 600, fontWeight: 600,
borderRadius: "12px", borderRadius: "13px",
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)", boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
transition: "all 0.2s", transition: "all 0.2s",
width: window.innerWidth < 640 ? "100%" : "auto", width: window.innerWidth < 640 ? "100%" : "auto",
@ -257,7 +259,6 @@ const HorariosDisponibilidade = ({
fontWeight: 500, fontWeight: 500,
}} }}
> >
(Novo)
</span> </span>
)} )}
</div> </div>
@ -295,8 +296,8 @@ const HorariosDisponibilidade = ({
key={day.dia} key={day.dia}
style={{ style={{
backgroundColor: "#f9fafb", backgroundColor: "#f9fafb",
padding: "20px", padding: "8px",
borderRadius: "12px", borderRadius: "10px",
border: "1px solid #e5e7eb", border: "1px solid #e5e7eb",
}} }}
> >
@ -365,11 +366,11 @@ const HorariosDisponibilidade = ({
<button <button
onClick={() => handleAddBlock(dayIndex)} onClick={() => handleAddBlock(dayIndex)}
style={{ style={{
marginTop: "24px", marginTop: "15px",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
padding: "12px 24px", padding: "10px 22px",
backgroundColor: "#10b981", backgroundColor: "#10b981",
color: "white", color: "white",
fontWeight: "bold", fontWeight: "bold",

View File

@ -1,5 +1,5 @@
import React, { createContext, useContext, useState, useEffect } from 'react'; import React, { createContext, useContext, useState, useEffect } from 'react';
import API_KEY from './apiKeys';
// 1. Criação do Contexto // 1. Criação do Contexto
const AuthContext = createContext(null); const AuthContext = createContext(null);
@ -41,6 +41,8 @@ export function AuthProvider({ children }) {
* @param {Object} tokenResponse O objeto completo retornado pelo Supabase Auth. * @param {Object} tokenResponse O objeto completo retornado pelo Supabase Auth.
*/ */
const setAuthTokens = (tokenResponse) => { const setAuthTokens = (tokenResponse) => {
console.log("TOKEN ADICIONADO")
if (tokenResponse && tokenResponse.access_token && tokenResponse.token_type) { if (tokenResponse && tokenResponse.access_token && tokenResponse.token_type) {
// 1. Atualiza o estado do React // 1. Atualiza o estado do React
setAccessToken(tokenResponse.access_token); setAccessToken(tokenResponse.access_token);
@ -77,6 +79,29 @@ export function AuthProvider({ children }) {
// --- VALOR DO CONTEXTO --- // --- VALOR DO CONTEXTO ---
const RefreshingToken = () => {
console.log("refresh token", refreshToken)
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("apikey", API_KEY)
var raw = JSON.stringify({
"refresh_token": refreshToken
});
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/token?grant_type=refresh_token`, requestOptions)
.then(response => response.json())
.then(result => setAuthTokens(result))
.catch(error => console.log('error', error));
}
const contextValue = { const contextValue = {
accessToken, accessToken,
tokenType, tokenType,
@ -85,8 +110,11 @@ export function AuthProvider({ children }) {
setAuthTokens, // Usado para CRIAR/MUDAR (Login/Refresh) setAuthTokens, // Usado para CRIAR/MUDAR (Login/Refresh)
clearAuthTokens, // Usado para MUDAR (Logout) clearAuthTokens, // Usado para MUDAR (Logout)
getAuthorizationHeader, // Usado para PEGAR (Endpoints) getAuthorizationHeader, // Usado para PEGAR (Endpoints)
RefreshingToken
}; };
return ( return (
<AuthContext.Provider value={contextValue}> <AuthContext.Provider value={contextValue}>
{children} {children}

View File

@ -1,25 +1,17 @@
import API_KEY from "../apiKeys"; import API_KEY from '../apiKeys';
const GetDoctorByID = async (ID,authHeader) => {
const GetDoctorByID = async (ID, authHeader) => {
var myHeaders = new Headers(); var myHeaders = new Headers();
myHeaders.append('apikey', API_KEY) myHeaders.append('apikey', API_KEY);
myHeaders.append('Authorization', authHeader) if (authHeader) myHeaders.append('Authorization', authHeader);
var requestOptions = { const requestOptions = { method: 'GET', redirect: 'follow', headers: myHeaders };
method: 'GET', const res = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${ID}`, requestOptions);
redirect: 'follow', const DictMedico = await res.json();
headers:myHeaders return DictMedico;
}; };
const result = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${ID}`, requestOptions)
const DictMedico = await result.json()
return DictMedico
}
const GetAllDoctors = async (authHeader) => { const GetAllDoctors = async (authHeader) => {
var myHeaders = new Headers(); var myHeaders = new Headers();

View File

@ -24,4 +24,9 @@ import API_KEY from "../apiKeys";
return userInfoData return userInfoData
} }
const UploadFotoAvatar = ( userID,access_token,file) => {
}
export {UserInfos} export {UserInfos}

View File

@ -0,0 +1,17 @@
import React from 'react';
import './style.css';
const CabecalhoError = ({ showCabecalho, message, errorCode }) => {
if (!showCabecalho) return null;
return (
<div className="cabecalho-error-wrapper">
<div className="cabecalho-error">
<p className='cabecalho-error-text'>{message}</p>
</div>
</div>
);
};
export default CabecalhoError;

View File

@ -0,0 +1,15 @@
function manager (setShowModal, RefreshingToken, setErrorInfo,errorData) {
console.log((errorData, "MANAGER"))
if(errorData.httpStatus === 401){
RefreshingToken()
}else{
// setErrorInfo(errorData)
setShowModal("modal");
}
}
export default manager

View File

@ -0,0 +1,64 @@
import React from "react";
import { useState, useEffect } from "react";
import { useAuth } from "../AuthProvider";
function ModalErro ({showModal, setShowModal, ErrorData}) {
const {RefreshingToken} = useAuth()
const [modalMensagem, setModalMensagem] = useState("")
console.log("eerrroror", ErrorData)
useEffect(() => {
if( ErrorData.httpStatus === 401){
setShowModal(false)
RefreshingToken()
console.log('uaua')
}else if(ErrorData.httpStatus === 404){
setModalMensagem("Erro interno do sistema")
}else{setModalMensagem(ErrorData.mensagem)}
}, [ErrorData])
return(
<div>
{showModal === "modal"?
<div className="modal-overlay">
<div className="modal-content">
<div className="modal-header bg-danger">
<h5 className="modal-title">{`(Erro ${ErrorData.httpStatus})`}</h5>
</div>
<div className="modal-body">
{modalMensagem}
</div>
<div className="modal-footer">
<button
className="modal-confirm-btn"
onClick={ () => setShowModal(false)}
>
Fechar
</button>
</div>
</div>
</div>
: null
}
</div>
);
}
export default ModalErro

View File

@ -0,0 +1,26 @@
.cabecalho-error {
background-color: #f3616d; /* vermelho forte */
color: white;
display: flex;
align-items: center; /* centraliza verticalmente */
justify-content: center; /* centraliza horizontalmente */
padding: 12px 20px;
margin: 10px;
border-radius: 8px;
font-weight: bold;
font-size: 16px;
width: calc(100% - 20px); /* ocupa quase toda a largura da div pai */
box-sizing: border-box;
}
.cabecalho-error-text {
margin: 0;
padding-left: 30px; /* espaço para "Erro 404" */
position: relative;
}
.cabecalho-error-code {
position: absolute;
left: 0;
font-weight: 700;
}

View File

@ -21,7 +21,7 @@
}, },
{ {
"name": "Relaototios", "name": "Relatórios",
"icon": "table", "icon": "table",
"url": "/admin/laudo" "url": "/admin/laudo"
}, },

View File

@ -21,60 +21,71 @@ import { Search } from 'lucide-react';
const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main) const Agendamento = ({setDictInfo}) => {
const navigate = useNavigate(); const navigate = useNavigate();
// Estados mesclados const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([])
const [selectedID, setSelectedId] = useState('0') // (main)
const [filaEsperaData, setfilaEsperaData] = useState([]) // (main) const [selectedID, setSelectedId] = useState('0')
const [filaEsperaData, setFilaEsperaData] = useState([])
const [FiladeEspera, setFiladeEspera] = useState(false); const [FiladeEspera, setFiladeEspera] = useState(false);
const [tabela, setTabela] = useState('diario'); const [tabela, setTabela] = useState('diario');
const [PageNovaConsulta, setPageConsulta] = useState(false); const [PageNovaConsulta, setPageConsulta] = useState(false);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [agendamentos, setAgendamentos] = useState() // (main) const [agendamentos, setAgendamentos] = useState()
const {getAuthorizationHeader} = useAuth() const {getAuthorizationHeader} = useAuth()
const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({}) const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({})
const [showDeleteModal, setShowDeleteModal] = useState(false) const [showDeleteModal, setShowDeleteModal] = useState(false)
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState() // (main) const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState()
const [ListaDeMedicos, setListaDeMedicos] = useState([]) const [ListaDeMedicos, setListaDeMedicos] = useState([])
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]) const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([])
const [searchTermDoctor, setSearchTermDoctor] = useState(''); const [searchTermDoctor, setSearchTermDoctor] = useState('');
const [MedicoFiltrado, setMedicoFiltrado] = useState({id:"vazio"})
const [cacheFiladeEspera, setcacheFiladeEspera] = useState([])
const [cacheAgendamentos, setCacheAgendamentos] = useState([])
let authHeader = getAuthorizationHeader() let authHeader = getAuthorizationHeader()
// Função FiltrarAgendamentos (Mesclado: Mantido o da MAIN, mais completo e com ordenação/fila de espera real) const cacheMedicos = {};
const FiltrarAgendamentos = async (listaTodosAgendamentos) => { const cachePacientes = {};
const ConfigurarFiladeEspera = async (patient_id, doctor_id, agendamento) => {
// Assumindo que GetDoctorByID e GetPatientByID estão definidos no seu escopo
let medico = await GetDoctorByID(doctor_id, authHeader);
let paciente = await GetPatientByID(patient_id, authHeader);
console.log(medico)
let dicionario = {
agendamento: agendamento, useMemo(() => {
if (!listaTodosAgendamentos.length) return { agendamentosOrganizados: {}, filaEsperaData: [] };
console.log("recarregando")
const DictAgendamentosOrganizados = {};
const ListaFilaDeEspera = [];
const fetchDados = async () => {
for (const agendamento of listaTodosAgendamentos) {
if (agendamento.status === "requested") {
// Cache de médico e paciente
if (!cacheMedicos[agendamento.doctor_id]) {
cacheMedicos[agendamento.doctor_id] = await GetDoctorByID(agendamento.doctor_id, authHeader);
}
if (!cachePacientes[agendamento.patient_id]) {
cachePacientes[agendamento.patient_id] = await GetPatientByID(agendamento.patient_id, authHeader);
}
const medico = cacheMedicos[agendamento.doctor_id];
const paciente = cachePacientes[agendamento.patient_id];
ListaFilaDeEspera.push({
agendamento,
Infos: { Infos: {
nome_medico: medico[0]?.full_name, nome_medico: medico[0]?.full_name,
doctor_id: medico[0]?.id, doctor_id: medico[0]?.id,
patient_id: paciente[0].id, patient_id: paciente[0]?.id,
paciente_nome: paciente[0].full_name, paciente_nome: paciente[0]?.full_name,
paciente_cpf: paciente[0].cpf paciente_cpf: paciente[0]?.cpf,
} },
}; });
return dicionario;
};
let DictAgendamentosOrganizados = {};
let ListaFilaDeEspera = [];
for (const agendamento of listaTodosAgendamentos) {
if (agendamento.status === 'requested') {
let v = await ConfigurarFiladeEspera(agendamento.patient_id, agendamento.doctor_id, agendamento);
ListaFilaDeEspera.push(v);
} else { } else {
const DiaAgendamento = agendamento.scheduled_at.split("T")[0]; const DiaAgendamento = agendamento.scheduled_at.split("T")[0];
@ -86,28 +97,28 @@ const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
} }
} }
// Ordenar por data
for (const DiaAgendamento in DictAgendamentosOrganizados) { for (const DiaAgendamento in DictAgendamentosOrganizados) {
DictAgendamentosOrganizados[DiaAgendamento].sort((a, b) => { DictAgendamentosOrganizados[DiaAgendamento].sort((a, b) => a.scheduled_at.localeCompare(b.scheduled_at));
if (a.scheduled_at < b.scheduled_at) return -1;
if (a.scheduled_at > b.scheduled_at) return 1;
return 0;
});
} }
const chavesOrdenadas = Object.keys(DictAgendamentosOrganizados).sort();
const chavesOrdenadas = Object.keys(DictAgendamentosOrganizados).sort((a, b) => { const DictAgendamentosFinal = {};
if (a < b) return -1;
if (a > b) return 1;
return 0;
});
let DictAgendamentosFinal = {};
for (const data of chavesOrdenadas) { for (const data of chavesOrdenadas) {
DictAgendamentosFinal[data] = DictAgendamentosOrganizados[data]; DictAgendamentosFinal[data] = DictAgendamentosOrganizados[data];
} }
setAgendamentosOrganizados(DictAgendamentosFinal); setAgendamentosOrganizados(DictAgendamentosFinal);
setfilaEsperaData(ListaFilaDeEspera); setFilaEsperaData(ListaFilaDeEspera);
}; };
fetchDados();
return { agendamentosOrganizados: DictAgendamentosOrganizados, filaEsperaData: ListaFilaDeEspera };
}, [listaTodosAgendamentos]); // 👉 só recalcula quando a lista muda
useEffect(() => { useEffect(() => {
var myHeaders = new Headers(); var myHeaders = new Headers();
myHeaders.append("Authorization", authHeader); myHeaders.append("Authorization", authHeader);
@ -121,7 +132,7 @@ const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions) 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(response => response.json())
.then(result => {FiltrarAgendamentos(result);console.log(result)}) .then(result => {setListaTodosAgendamentos(result);console.log(result)})
.catch(error => console.log('error', error)); .catch(error => console.log('error', error));
const PegarTodosOsMedicos = async () => { const PegarTodosOsMedicos = async () => {
@ -135,56 +146,33 @@ const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
PegarTodosOsMedicos() PegarTodosOsMedicos()
}, []) }, [])
useEffect(() => {
console.log("mudou FiltredTodosMedicos:", FiltredTodosMedicos);
if (FiltredTodosMedicos.length === 1) {
const unicoMedico = FiltredTodosMedicos[0];
console.log(unicoMedico)
const idMedicoFiltrado = unicoMedico.idMedico;
console.log(`Médico único encontrado: ${unicoMedico.nomeMedico}. ID: ${idMedicoFiltrado}`);
const agendamentosDoMedico = filtrarAgendamentosPorMedico(
DictAgendamentosOrganizados,
idMedicoFiltrado
);
console.log(`Total de agendamentos filtrados para este médico: ${agendamentosDoMedico.length}`);
console.log("Lista completa de Agendamentos do Médico:", agendamentosDoMedico);
FiltrarAgendamentos(agendamentosDoMedico)
}
}, [FiltredTodosMedicos]);
const deleteConsulta = (selectedPatientId) => { const deleteConsulta = (selectedPatientId) => {
console.log("tentando apagar")
var myHeaders = new Headers(); var myHeaders = new Headers();
myHeaders.append("Authorization", authHeader); myHeaders.append("Content-Type", "application/json");
myHeaders.append("apikey", API_KEY) myHeaders.append('apikey', API_KEY)
myHeaders.append("authorization", authHeader)
var requestOptions = {
method: 'DELETE',
redirect: 'follow',
headers: myHeaders
};
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${selectedPatientId}`, requestOptions) var raw = JSON.stringify({ "status":"cancelled"
.then(response => response.json()) });
var requestOptions = {
method: 'PATCH',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${selectedPatientId}`, requestOptions)
.then(response => {if(response.status !== 200)(console.log(response))})
.then(result => console.log(result)) .then(result => console.log(result))
.catch(error => console.log('error', error)); .catch(error => console.log('error', error));
} }
const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
const todasAsListasDeAgendamentos = Object.values(dictAgendamentos);
const todosOsAgendamentos = todasAsListasDeAgendamentos.flat();
const agendamentosFiltrados = todosOsAgendamentos.filter(agendamento =>
agendamento.doctor_id === idMedicoFiltrado
);
return agendamentosFiltrados;
};
const filteredAgendamentos = useMemo(() => { const filteredAgendamentos = useMemo(() => {
@ -233,12 +221,59 @@ const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
}; };
useEffect(() => {
console.log("mudou FiltredTodosMedicos:", FiltredTodosMedicos);
if (MedicoFiltrado.id != "vazio" ) {
const unicoMedico = MedicoFiltrado;
console.log(unicoMedico)
const idMedicoFiltrado = unicoMedico.idMedico;
console.log(`Médico único encontrado: ${unicoMedico.nomeMedico}. ID: ${idMedicoFiltrado}`);
const agendamentosDoMedico = filtrarAgendamentosPorMedico(
DictAgendamentosOrganizados,
idMedicoFiltrado
);
console.log(`Total de agendamentos filtrados para este médico: ${agendamentosDoMedico.length}`);
console.log("Lista completa de Agendamentos do Médico:", agendamentosDoMedico);
//FiltrarAgendamentos(agendamentosDoMedico)
setListaTodosAgendamentos(agendamentosDoMedico)
}
}, [FiltredTodosMedicos, MedicoFiltrado]);
const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
setCacheAgendamentos(DictAgendamentosOrganizados);
const todasAsListasDeAgendamentos = Object.values(dictAgendamentos);
const todosOsAgendamentos = todasAsListasDeAgendamentos.flat();
const agendamentosFiltrados = todosOsAgendamentos.filter(agendamento =>
agendamento.doctor_id === idMedicoFiltrado
);
return agendamentosFiltrados;
};
const handleSearchMedicos = (term) => { const handleSearchMedicos = (term) => {
setSearchTermDoctor(term); setSearchTermDoctor(term);
if (term.trim() === '') { if (term.trim() === '') {
if(MedicoFiltrado.id !== "vazio"){
console.log("Medico escolhido, mas vai ser apagado")
console.log(cacheAgendamentos, "cache ")
setAgendamentosOrganizados(cacheAgendamentos)
}
setFiltredTodosMedicos([]); setFiltredTodosMedicos([]);
setMedicoFiltrado({id:"vazio"})
//2 FiltrarAgendamentos()
return; return;
} }
if (FiltredTodosMedicos.length === 1){
setMedicoFiltrado({...FiltredTodosMedicos[0]})
}
const filtered = ListaDeMedicos.filter(medico => const filtered = ListaDeMedicos.filter(medico =>
medico.nomeMedico.toLowerCase().includes(term.toLowerCase()) medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
@ -247,8 +282,6 @@ const handleSearchMedicos = (term) => {
}; };
const handleClickCancel = () => setPageConsulta(false)
return ( return (
<div> <div>
<h1>Agendar nova consulta</h1> <h1>Agendar nova consulta</h1>
@ -301,6 +334,9 @@ const handleSearchMedicos = (term) => {
className='dropdown-item' className='dropdown-item'
onClick={() => { onClick={() => {
setSearchTermDoctor(medico.nomeMedico); setSearchTermDoctor(medico.nomeMedico);
setFiltredTodosMedicos([]);
setMedicoFiltrado(medico)
}} }}
> >
<p>{medico.nomeMedico} </p> <p>{medico.nomeMedico} </p>
@ -331,6 +367,7 @@ const handleSearchMedicos = (term) => {
}} }}
> >
Fila de espera Fila de espera
</button> </button>
</div> </div>
@ -432,7 +469,7 @@ const handleSearchMedicos = (term) => {
<AgendamentoCadastroManager setPageConsulta={setPageConsulta} /> <AgendamentoCadastroManager setPageConsulta={setPageConsulta} />
)} )}
{/* Modal de Confirmação de Exclusão (Da MAIN) */} {/* Modal de Confirmação de Exclusão */}
{showDeleteModal && ( {showDeleteModal && (
<div <div
className="modal fade show" className="modal fade show"

View File

@ -3,40 +3,96 @@ import { Link } from "react-router-dom";
const ENDPOINT_LISTAR = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability"; const ENDPOINT_LISTAR = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability";
const MEDICOS_MOCKADOS = [
{ id: 53, nome: " João Silva" },
{ id: 19, nome: " Ana Costa" },
{ id: 11, nome: " Pedro Santos" },
];
const diasDaSemana = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"];
const formatarDataHora = (isoString) => {
if (!isoString) return "N/A";
try {
const data = new Date(isoString);
return data.toLocaleTimeString("pt-BR", { hour: '2-digit', minute: '2-digit', timeZone: 'UTC' });
} catch (error) {
return "Data Inválida";
}
};
const DisponibilidadesDoctorPage = () => { const DisponibilidadesDoctorPage = () => {
const [disponibilidades, setDisponibilidades] = useState([]); const [disponibilidades, setDisponibilidades] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [filtroMedicoId, setFiltroMedicoId] = useState(""); const [filtroMedicoNome, setFiltroMedicoNome] = useState("");
const [filtroActive, setFiltroActive] = useState("true");
const [medicoValido, setMedicoValido] = useState(false);
const fetchDisponibilidades = useCallback(async (doctorId, activeStatus) => { const [medicoEncontradoId, setMedicoEncontradoId] = useState(null);
const encontrarMedicoIdPorNome = (nome) => {
if (!nome) return null;
const termoBusca = nome.toLowerCase();
const medico = MEDICOS_MOCKADOS.find(m =>
m.nome.toLowerCase().includes(termoBusca)
);
return medico ? medico.id : null;
};
const fetchDisponibilidades = useCallback(async (nome) => {
setLoading(true); setLoading(true);
let url = `${ENDPOINT_LISTAR}?select=*`; setDisponibilidades([]);
if (doctorId) url += `&doctor_id=eq.${doctorId}`; setMedicoEncontradoId(null);
if (activeStatus === "true" || activeStatus === "false") url += `&active=eq.${activeStatus}`;
const doctorId = encontrarMedicoIdPorNome(nome);
if (!doctorId) {
setLoading(false);
return;
}
const url = `${ENDPOINT_LISTAR}?select=*&doctor_id=eq.${doctorId}`;
try { try {
const response = await fetch(url); const response = await fetch(url);
const result = await response.json(); const result = await response.json();
setDisponibilidades(Array.isArray(result) ? result : []);
setMedicoValido(Array.isArray(result) && result.length > 0); let dados = Array.isArray(result) ? result : [];
setDisponibilidades(dados);
setMedicoEncontradoId(doctorId);
} catch (error) { } catch (error) {
setDisponibilidades([]); setDisponibilidades([]);
setMedicoValido(false);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, []);
useEffect(() => { useEffect(() => {
if (filtroMedicoId) { if (filtroMedicoNome) {
fetchDisponibilidades(filtroMedicoId, filtroActive); const timer = setTimeout(() => {
fetchDisponibilidades(filtroMedicoNome);
}, 300);
return () => clearTimeout(timer);
} else { } else {
setDisponibilidades([]); setDisponibilidades([]);
setMedicoValido(false); setMedicoEncontradoId(null);
} }
}, [filtroMedicoId, filtroActive, fetchDisponibilidades]); }, [filtroMedicoNome, fetchDisponibilidades]);
const rotaGerenciar = medicoEncontradoId
? `../medicos/${medicoEncontradoId}/edit`
: `../medicos/novo/edit`;
return ( return (
<div id="main-content"> <div id="main-content">
@ -44,8 +100,9 @@ const DisponibilidadesDoctorPage = () => {
<h1 style={{ fontSize: "1.5rem", fontWeight: "bold", color: "#333" }}> <h1 style={{ fontSize: "1.5rem", fontWeight: "bold", color: "#333" }}>
Disponibilidades por Médico Disponibilidades por Médico
</h1> </h1>
<Link <Link
to={medicoValido ? `../medicos/${filtroMedicoId}/edit` : "#"} to={rotaGerenciar}
className="btn-primary" className="btn-primary"
style={{ style={{
padding: "10px 20px", padding: "10px 20px",
@ -53,8 +110,6 @@ const DisponibilidadesDoctorPage = () => {
whiteSpace: "nowrap", whiteSpace: "nowrap",
textDecoration: "none", textDecoration: "none",
display: "inline-block", display: "inline-block",
opacity: medicoValido ? 1 : 0.6,
pointerEvents: medicoValido ? "auto" : "none",
}} }}
> >
+ Gerenciar Disponibilidades + Gerenciar Disponibilidades
@ -63,28 +118,16 @@ const DisponibilidadesDoctorPage = () => {
<div className="atendimento-eprocura"> <div className="atendimento-eprocura">
<div className="busca-atendimento"> <div className="busca-atendimento">
<div> <div style={{ marginRight: '10px' }}>
<i className="fa-solid fa-user-doctor"></i> <i className="fa-solid fa-user-doctor"></i>
<input <input
type="text" type="text"
placeholder="Filtrar por ID do Médico..." placeholder="Filtrar por Nome do Médico..."
value={filtroMedicoId} value={filtroMedicoNome}
onChange={(e) => setFiltroMedicoId(e.target.value)} onChange={(e) => setFiltroMedicoNome(e.target.value)}
style={{ border: "1px solid #ccc", borderRadius: "4px", padding: "5px" }} style={{ border: "1px solid #ccc", borderRadius: "4px", padding: "5px" }}
/> />
</div> </div>
<div>
<i className="fa-solid fa-check-circle"></i>
<select
value={filtroActive}
onChange={(e) => setFiltroActive(e.target.value)}
style={{ padding: "8px", border: "1px solid #ccc", borderRadius: "4px" }}
>
<option value="true">Ativas</option>
<option value="false">Inativas</option>
<option value="">Todas</option>
</select>
</div>
</div> </div>
<section className="calendario-ou-filaespera"> <section className="calendario-ou-filaespera">
@ -95,15 +138,15 @@ const DisponibilidadesDoctorPage = () => {
{loading ? ( {loading ? (
<p className="text-center py-10">Carregando disponibilidades...</p> <p className="text-center py-10">Carregando disponibilidades...</p>
) : disponibilidades.length === 0 ? ( ) : (filtroMedicoNome && disponibilidades.length === 0) ? (
<p className="text-center py-10"> <p className="text-center py-10">
Nenhuma disponibilidade encontrada para os filtros aplicados. Nenhuma disponibilidade encontrada para o nome buscado.
</p> </p>
) : ( ) : (
<table className="fila-tabela" style={{ width: "100%", borderCollapse: "collapse" }}> <table className="fila-tabela" style={{ width: "100%", borderCollapse: "collapse" }}>
<thead> <thead>
<tr> <tr>
{["ID", "ID Médico", "Dia da Semana", "Início", "Término", "Intervalo (min)", "Tipo Consulta", "Status"].map( {[ "Dia da Semana", "Início", "Término", "Intervalo", "Tipo Consulta"].map(
(header) => ( (header) => (
<th <th
key={header} key={header}
@ -118,18 +161,17 @@ const DisponibilidadesDoctorPage = () => {
<tbody> <tbody>
{disponibilidades.map((disp, index) => ( {disponibilidades.map((disp, index) => (
<tr key={disp.id || index} style={{ borderBottom: "1px solid #eee" }}> <tr key={disp.id || index} style={{ borderBottom: "1px solid #eee" }}>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.id || "N/A"}</td> <td style={{ padding: "10px", fontSize: "0.9em" }}>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.doctor_id}</td> {diasDaSemana[disp.weekday] || disp.weekday}
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.weekday}</td> </td>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.start_time || "N/A"}</td> <td style={{ padding: "10px", fontSize: "0.9em" }}>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.end_time || "N/A"}</td> {formatarDataHora(disp.start_time)}
</td>
<td style={{ padding: "10px", fontSize: "0.9em" }}>
{formatarDataHora(disp.end_time)}
</td>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.slot_minutes}</td> <td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.slot_minutes}</td>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.appointment_type}</td> <td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.appointment_type}</td>
<td style={{ padding: "10px", fontSize: "0.9em" }}>
<span className={`status-tag ${disp.active ? "legenda-item-realizado" : "legenda-item-cancelado"}`}>
{disp.active ? "Ativa" : "Inativa"}
</span>
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@ -9,12 +9,25 @@ function ForgotPassword() {
setEmail(e.target.value); setEmail(e.target.value);
}; };
const handleSubmit = (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
if (email) { if (email) {
// Simulate sending email try {
const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/auth/v1/otp", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email })
});
if (response.ok) {
setAlert("E-mail de verificação enviado!"); setAlert("E-mail de verificação enviado!");
// You can add your actual email logic here console.log("Magic link enviado para:", email);
} else {
setAlert("Não foi possível enviar o e-mail. Tente novamente.");
}
} catch (error) {
setAlert("Erro ao enviar e-mail. Tente novamente.");
console.error("Falha ao enviar magic link:", error);
}
} else { } else {
setAlert("Preencha o campo de e-mail!"); setAlert("Preencha o campo de e-mail!");
} }

View File

@ -1,11 +1,13 @@
import React, { useState, useEffect, use } from "react"; import React, { useState, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { useAuth } from "../components/utils/AuthProvider"; import { useAuth } from "../components/utils/AuthProvider";
import API_KEY from "../components/utils/apiKeys"; import API_KEY from "../components/utils/apiKeys";
import { UserInfos } from "../components/utils/Functions-Endpoints/General"; import { UserInfos } from "../components/utils/Functions-Endpoints/General";
import CabecalhoError from "../components/utils/fetchErros/CabecalhoError";
function Login({ onEnterSystem }) { function Login({ onEnterSystem }) {
const { setAuthTokens } = useAuth(); const { setAuthTokens } = useAuth();
const [showCabecalho, setShowCabecalho ] = useState(false)
const navigate = useNavigate(); const navigate = useNavigate();
const [form, setForm] = useState({ const [form, setForm] = useState({
username: "", username: "",
@ -126,6 +128,9 @@ function Login({ onEnterSystem }) {
} else if (UserData?.roles?.includes("financeiro")) { } else if (UserData?.roles?.includes("financeiro")) {
navigate(`/financeiro/`); navigate(`/financeiro/`);
} }
}else{
console.log("ERROROROROROOR")
setShowCabecalho(true)
} }
} else { } else {
setAlert("Preencha todos os campos!"); setAlert("Preencha todos os campos!");
@ -135,9 +140,9 @@ function Login({ onEnterSystem }) {
return ( return (
<> <>
<div className="mt-3 card-position"> <div className="mt-3 card-position">
<div className="col-lg-5 col-md-7 col-sm-9 col-12 mx-auto"> <div className="col-lg-5 col-md-7 col-sm-9 col-5 mx-auto">
<div className="card shadow-sm d-flex justify-content-between align-items-center"> <div className="card w-10 shadow-sm d-flex justify-content-between align-items-center">
<div id="auth-left" className="w-100"> <div id="auth-left" className="w-10">
<div className="auth-logo"> <div className="auth-logo">
<br /> <br />
<Link to="/"> <Link to="/">
@ -148,11 +153,7 @@ function Login({ onEnterSystem }) {
<p className="auth-subtitle mb-5"> <p className="auth-subtitle mb-5">
Entre com os dados que você inseriu durante o registro. Entre com os dados que você inseriu durante o registro.
</p> </p>
{alert && ( <CabecalhoError showCabecalho={showCabecalho} message={"E-mail ou senha incorretos."}/>
<div className="alert alert-info" role="alert">
{alert}
</div>
)}
<form onSubmit={handleLogin}> <form onSubmit={handleLogin}>
<div className="form-group position-relative has-icon-left mb-4"> <div className="form-group position-relative has-icon-left mb-4">
<input <input

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
function Register() { function Register() {
const navigate = useNavigate(); const navigate = useNavigate();
const [form, setForm] = useState({ const [form, setForm] = useState({
@ -14,11 +15,12 @@ function Register() {
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const handleChange = (e) => { const handleChange = (e) => {
setForm({ ...form, [e.target.name]: e.target.value }); setForm({ ...form, [e.target.name]: e.target.value });
}; };
const handleLogin = (e) => { const handleLogin = async (e) => {
e.preventDefault(); e.preventDefault();
if ( if (
form.email && form.email &&
@ -31,18 +33,98 @@ function Register() {
setAlert("As senhas não coincidem!"); setAlert("As senhas não coincidem!");
return; return;
} }
// ...register logic... try {
navigate('/secretaria/inicio'); const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/auth/v1/otp", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: form.email,
username: form.username,
userType: form.userType,
password: form.password
})
});
if (response.ok) {
setAlert("Cadastro realizado! Verifique seu e-mail.");
console.log("Usuário cadastrado:", form.email);
} else {
setAlert("Não foi possível cadastrar. Tente novamente.");
}
} catch (error) {
setAlert("Erro ao cadastrar. Tente novamente.");
console.error("Falha ao cadastrar usuário:", error);
}
} else { } else {
setAlert("Preencha todos os campos!"); setAlert("Preencha todos os campos!");
} }
}; };
const loginWithPassword = async (email, password) => {
try {
const response = await fetch('https://mock.apidog.com/m1/1053378-0-default/auth/v1/token?grant_type=password', {
method: 'POST',
headers: {
'Authorization': 'Bearer <seu-token>',
'apikey': '<sua-api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: password
})
});
const data = await response.json();
if (response.ok) {
console.log("Login bem-sucedido:", data);
setAlert("Login realizado com sucesso!");
} else {
console.error("Falha no login:", data);
setAlert(data.message || "E-mail ou senha incorretos.");
}
} catch (error) {
console.error("Erro na chamada de login:", error);
setAlert("Ocorreu um erro de rede ao tentar fazer login.");
}
};
const sendMagicLink = async (email) => {
const magicLinkEndpoint = "https://mock.apidog.com/m1/1053378-0-default/auth/v1/otp";
try {
const response = await fetch(magicLinkEndpoint, {
method: 'POST',
headers: {
'Authorization': 'Bearer <seu-token>',
'apikey': '<sua-api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email
})
});
const data = await response.json();
if (response.ok) {
console.log("Magic link enviado:", data);
setAlert("Magic link enviado para o seu e-mail! Verifique sua caixa de entrada.");
} else {
console.error("Falha ao enviar magic link:", data);
setAlert(data.message || "Não foi possível enviar o magic link.");
}
} catch (error) {
console.error("Erro na chamada de envio de magic link:", error);
setAlert("Ocorreu um erro de rede ao enviar o magic link.");
}
};
return ( return (
<> <>
<div className="mt-3 card-position"> <div className="mt-3 card-position">
<div className="col-lg-5 col-12"> <div className="col-lg-5 col-6">
<div className="card shadow-sm d-flex justify-content-between align-items-center"> <div className="card w-10 shadow-sm d-flex justify-content-between align-items-center">
<div id="auth-left"> <div id="auth-left">
<div className="auth-logo"> <div className="auth-logo">
<br /> <br />
@ -74,40 +156,8 @@ function Register() {
<i className="bi bi-envelope" /> <i className="bi bi-envelope" />
</div> </div>
</div> </div>
<div className="form-group position-relative has-icon-left mb-4">
<input
type="text"
name="username"
className="form-control form-control-xl"
placeholder="Nome de usuário"
value={form.username}
onChange={handleChange}
required
/>
<div className="form-control-icon">
<i className="bi bi-person" />
</div>
</div>
<div className="form-group position-relative has-icon-left mb-4">
<select
name="userType"
className="form-control form-control-xl"
value={form.userType}
onChange={handleChange}
required
>
<option value="" disabled>
Selecione o tipo de usuário
</option>
<option value="paciente">Paciente</option>
<option value="secretaria">Secretaria</option>
<option value="medico">Médico</option>
<option value="admin">Admin</option>
</select>
<div className="form-control-icon">
<i className="bi bi-person" />
</div>
</div>
<div className="form-group position-relative has-icon-left mb-4"> <div className="form-group position-relative has-icon-left mb-4">
<input <input
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
@ -179,4 +229,5 @@ function Register() {
); );
} }
export default Register; export default Register;

View File

@ -3,10 +3,12 @@ import { Link } from "react-router-dom";
import API_KEY from "../components/utils/apiKeys"; import API_KEY from "../components/utils/apiKeys";
import { useAuth } from "../components/utils/AuthProvider"; import { useAuth } from "../components/utils/AuthProvider";
import "./style/TablePaciente.css"; import "./style/TablePaciente.css";
import ModalErro from "../components/utils/fetchErros/ModalErro";
import manager from "../components/utils/fetchErros/ManagerFunction";
function TablePaciente({ setCurrentPage, setPatientID }) { function TablePaciente({ setCurrentPage, setPatientID }) {
const { getAuthorizationHeader, isAuthenticated } = useAuth(); const { getAuthorizationHeader, isAuthenticated, RefreshingToken } = useAuth();
const [pacientes, setPacientes] = useState([]); const [pacientes, setPacientes] = useState([]);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
@ -24,6 +26,10 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [selectedPatientId, setSelectedPatientId] = useState(null); const [selectedPatientId, setSelectedPatientId] = useState(null);
const [showModalError, setShowModalError] = useState("");
const [ ErrorInfo, setErrorInfo] = useState({})
const GetAnexos = async (id) => { const GetAnexos = async (id) => {
var myHeaders = new Headers(); var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer <token>"); myHeaders.append("Authorization", "Bearer <token>");
@ -100,7 +106,6 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
}; };
useEffect(() => { useEffect(() => {
const authHeader = getAuthorizationHeader() const authHeader = getAuthorizationHeader()
console.log(authHeader, 'aqui autorização') console.log(authHeader, 'aqui autorização')
@ -114,10 +119,41 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
redirect: 'follow' redirect: 'follow'
}; };
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions) fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patient", requestOptions)
.then(response => response.json()) .then(response => {
.then(result => setPacientes(result))
.catch(error => console.log('error', error)); // 1. VERIFICAÇÃO DO STATUS HTTP (Se não for 2xx)
if (!response.ok) {
return response.json().then(errorData => {
const errorObject = {
httpStatus: response.status,
apiCode: errorData.code,
message: errorData.message || response.statusText,
details: errorData.details,
hint: errorData.hint
};
console.error("ERRO DETALHADO:", errorObject);
throw errorObject;
});
}
// 3. Se a resposta for OK (2xx), processamos o JSON normalmente
return response.json();
})
.then(result => {
setPacientes(result);
console.log("Sucesso:", result);
setShowModalError(false);
})
.catch(error => {
manager(setShowModalError, RefreshingToken, setErrorInfo, error)
});
}, [isAuthenticated, getAuthorizationHeader]); }, [isAuthenticated, getAuthorizationHeader]);
const ehAniversariante = (dataNascimento) => { const ehAniversariante = (dataNascimento) => {
@ -199,11 +235,12 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
}) : []; }) : [];
useEffect(() => { useEffect(() => {
console.log(` Pacientes totais: ${pacientes.length}, Filtrados: ${pacientesFiltrados.length}`); console.log(` Pacientes totais: ${pacientes?.length}, Filtrados: ${pacientesFiltrados?.length}`);
}, [pacientes, pacientesFiltrados, search]); }, [pacientes, pacientesFiltrados, search]);
return ( return (
<> <>
<ModalErro showModal={showModalError} setShowModal={setShowModalError} ErrorData={ErrorInfo}/>
<div className="page-heading"> <div className="page-heading">
<h3>Lista de Pacientes</h3> <h3>Lista de Pacientes</h3>
</div> </div>
@ -382,7 +419,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
<div className="mb-3"> <div className="mb-3">
<span className="badge results-badge"> <span className="badge results-badge">
{pacientesFiltrados.length} de {pacientes.length} pacientes encontrados {pacientesFiltrados?.length} de {pacientes?.length} pacientes encontrados
</span> </span>
</div> </div>

View File

@ -1,21 +1,27 @@
import { Routes, Route } from "react-router-dom"; import { Routes, Route } from "react-router-dom";
import Sidebar from "../../components/Sidebar"; import Sidebar from "../../components/Sidebar";
import PacienteItems from "../../data/sidebar-items-paciente.json"; import PacienteItems from "../../data/sidebar-items-paciente.json";
import { useState } from "react";
import LaudoManager from "../../pages/LaudoManager"; import LaudoManager from "../../pages/LaudoManager";
import ConsultaCadastroManager from "../../PagesPaciente/ConsultaCadastroManager"; import ConsultaCadastroManager from "../../PagesPaciente/ConsultaCadastroManager";
import ConsultasPaciente from "../../PagesPaciente/ConsultasPaciente"; import ConsultasPaciente from "../../PagesPaciente/ConsultasPaciente";
import ConsultaEditPage from "../../PagesPaciente/ConsultaEditPage";
function PerfilPaciente({ onLogout }) { function PerfilPaciente({ onLogout }) {
const [dadosConsulta, setConsulta] = useState({})
return ( return (
<div id="app" className="active"> <div id="app" className="active">
<Sidebar onLogout={onLogout} menuItems={PacienteItems} /> <Sidebar onLogout={onLogout} menuItems={PacienteItems} />
<div id="main"> <div id="main">
<Routes> <Routes>
<Route path="/" element={<LaudoManager />} /> <Route path="/" element={<LaudoManager />} />
<Route path="agendamento" element={<ConsultasPaciente />} /> <Route path="agendamento" element={<ConsultasPaciente setConsulta={setConsulta}/>} />
<Route path="agendamento/criar" element={<ConsultaCadastroManager />} /> <Route path="agendamento/criar" element={<ConsultaCadastroManager />} />
<Route path="agendamento/edit" element={<ConsultaEditPage dadosConsulta={dadosConsulta} />} />
<Route path="laudo" element={<LaudoManager />} /> <Route path="laudo" element={<LaudoManager />} />
<Route path="*" element={<h2>Página não encontrada</h2>} /> <Route path="*" element={<h2>Página não encontrada</h2>} />
</Routes> </Routes>