Compare commits
21 Commits
503a03e0b4
...
15062ca32e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15062ca32e | ||
|
|
8a3d6e0305 | ||
|
|
a5cd4d3447 | ||
|
|
a8fc3eb397 | ||
|
|
95054bb9c1 | ||
|
|
844ebf03a8 | ||
|
|
b0306125ae | ||
| 38ebb5f7f3 | |||
| e4386c1776 | |||
| d80b13e404 | |||
| 272be628aa | |||
|
|
6d18a23dd7 | ||
|
|
1ba45e6c67 | ||
|
|
cf3ea901b8 | ||
|
|
aa399d2d99 | ||
|
|
191e997180 | ||
|
|
8878ad6ced | ||
| ffa909734f | |||
|
|
c6523f1b0d | ||
|
|
edfe985512 | ||
|
|
fe488e4dd0 |
@ -1,56 +0,0 @@
|
||||
[33m3993097[m[33m ([m[1;36mHEAD[m[33m -> [m[1;32mmain[m[33m)[m Merge branch 'main' of https://git.popcode.com.br/RiseUP/riseup-squad23
|
||||
[33m63659b6[m Verificação do cpf e colocar o erro 404
|
||||
[33mecae83c[m[33m ([m[1;31mriseup/main[m[33m, [m[1;31mriseup/HEAD[m[33m, [m[1;31morigin/main[m[33m, [m[1;31morigin/HEAD[m[33m)[m Merge pull request 'Conectando-o-resto-das-API' (#2) from Conectando-o-resto-das-API into main
|
||||
[33m908d545[m[33m ([m[1;31mriseup/Conectando-o-resto-das-API[m[33m, [m[1;31morigin/Conectando-o-resto-das-API[m[33m)[m feat: adicionar upload e delete de anexos do paciente
|
||||
[33m4b404c0[m Merge branch 'main' of https://git.popcode.com.br/RiseUP/riseup-squad23
|
||||
[33mbd20c2d[m Merge remote-tracking branch 'origin/main'
|
||||
[33m8aeabd1[m[33m ([m[1;31mriseup/Fix-dos-erros-do-projeto[m[33m, [m[1;31morigin/Fix-dos-erros-do-projeto[m[33m)[m FIx: todos os erros que aparecia no console foram resolvidos
|
||||
[33m0e29e7d[m melhorias na organização de pastas
|
||||
[33m98f076a[m Mergin com TableMelhorias
|
||||
[33m589d590[m Mergin com novas alterações de laudo
|
||||
[33m7b28e2a[m Details melhorias
|
||||
[33m9480edc[m[33m ([m[1;31mriseup/PaginaDetalhes[m[33m, [m[1;31morigin/PaginaDetalhes[m[33m)[m Pàgina detalhes
|
||||
[33me4515cf[m Adição das cores nos cards de consulta
|
||||
[33md3dd2fd[m[33m ([m[1;31mriseup/TableMelhorias[m[33m, [m[1;31morigin/TableMelhorias[m[33m)[m Detalhe nas tabelas
|
||||
[33ma54b119[m Delete Anexos apos pacientes forem excluidos
|
||||
[33m6e93cb5[m atualizar paciente
|
||||
[33mb9a35be[m começo do concerto do editar
|
||||
[33m82469bc[m Details funcional
|
||||
[33mcdfe4ea[m Validação de CPF
|
||||
[33m57c8f67[m[33m ([m[1;31mriseup/DetalhesMedico[m[33m, [m[1;31morigin/DetalhesMedico[m[33m)[m Detalhes do medico
|
||||
[33mb021444[m Mudanças formularios e detalhes
|
||||
[33md5d03b0[m[33m ([m[1;31mriseup/mudanças-de-laudo[m[33m, [m[1;31morigin/mudanças-de-laudo[m[33m)[m atualização do laudo
|
||||
[33ma502bbd[m agendamentos no incio
|
||||
[33m8e1fcd9[m Merge branch 'feature/novo-cadastro-paciente'
|
||||
[33mbea9076[m Merge remote-tracking branch 'origin/PaginaDetalhes'
|
||||
[33me35f217[m mergin branch inicio com main
|
||||
[33m1af8268[m Atualizacão do laudo
|
||||
[33m725d60d[m feat: ajeitei o nome
|
||||
[33mbab85ff[m[33m ([m[1;31mriseup/AgendamentoSidebar[m[33m, [m[1;31morigin/AgendamentoSidebar[m[33m)[m Concertar Agendamento
|
||||
[33mb2707e3[m Refatora o estilo do formulário do paciente para uma aparência de cartão com tipografia maior
|
||||
[33m37e8959[m Refatora o estilo do formulário do paciente para uma aparência de cartão com tipografia maior
|
||||
[33m0930385[m feat: uma piquena mudança
|
||||
[33mf6a19c4[m feat: Adiciona formulário de cadastro de paciente
|
||||
[33md91b5cf[m form de agendar consulta melhorado
|
||||
[33m0a60dd7[m Tabela semana e mes
|
||||
[33m7f07950[m[33m ([m[1;31mriseup/feature-Melhoria-no-Dashboard[m[33m, [m[1;31morigin/feature-Melhoria-no-Dashboard[m[33m)[m feat: Criação da página início e melhoria na navegação
|
||||
[33m39e25ad[m Pagina de detalhes atualizada
|
||||
[33m4f84791[m pequenas mudanaças na tabela de semana e mes
|
||||
[33m6737955[m form para nova consulta e tabelas de horario
|
||||
[33m26ded17[m Nova pagina de detalhes
|
||||
[33m874de84[m Inicio do agendamento
|
||||
[33mf3e7470[m[33m ([m[1;31mriseup/gerenciamento-de-laudo[m[33m, [m[1;31morigin/gerenciamento-de-laudo[m[33m)[m Laudo do Paciente
|
||||
[33m709cd4e[m Merge finalizado
|
||||
[33md6b3e86[m Merge detalhes-do-pacientes para main
|
||||
[33m08ffa55[m Merge remote-tracking branch 'origin/CrudMedico'
|
||||
[33m70c4d5f[m Termino da organização
|
||||
[33medd567d[m Inicio da organização
|
||||
[33m9c09113[m Mudanças pos feedback de davi
|
||||
[33maa3a5fa[m Criação da página dos detalhes dos pacientes
|
||||
[33m5534568[m Inicio de detalhes e atualização do paciente
|
||||
[33m06ff7d5[m Funcionalidade de delete e botão de opções
|
||||
[33m5b63fa2[m Mascara telefones
|
||||
[33mfb9d783[m adição da mascara do CPF
|
||||
[33ma489d84[m metodo GET e POST
|
||||
[33m4eaabbd[m first commit
|
||||
[33ma244691[m Initial commit
|
||||
768
package-lock.json
generated
768
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -10,9 +10,11 @@
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@tiptap/core": "^3.7.2",
|
||||
"@tiptap/extension-placeholder": "^3.7.1",
|
||||
"@tiptap/pm": "^3.7.2",
|
||||
"@tiptap/react": "^3.7.1",
|
||||
"@tiptap/starter-kit": "^3.7.1",
|
||||
"@tiptap/starter-kit": "^3.7.2",
|
||||
"apexcharts": "^5.3.4",
|
||||
"bootstrap": "^5.3.8",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
|
||||
@ -1,82 +1,97 @@
|
||||
// src/PagesMedico/DoctorRelatorioManager.jsx
|
||||
import API_KEY from '../components/utils/apiKeys';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {useState, useEffect} from 'react'
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAuth } from '../components/utils/AuthProvider';
|
||||
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient';
|
||||
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import html2pdf from 'html2pdf.js';
|
||||
import TiptapViewer from './TiptapViewer';
|
||||
|
||||
const DoctorRelatorioManager = () => {
|
||||
const navigate = useNavigate()
|
||||
const {getAuthorizationHeader} = useAuth();
|
||||
let authHeader = getAuthorizationHeader()
|
||||
const [RelatoriosFiltrados, setRelatorios] = useState([])
|
||||
const [PacientesComRelatorios, setPacientesComRelatorios] = useState([])
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [index, setIndex] = useState()
|
||||
// 1º useEffect: Busca os dados dos pacientes após carregar os relatórios
|
||||
useEffect( () => {
|
||||
let pacientesDosRelatorios = []
|
||||
const navigate = useNavigate();
|
||||
const { getAuthorizationHeader } = useAuth();
|
||||
const authHeader = getAuthorizationHeader();
|
||||
const [RelatoriosFiltrados, setRelatorios] = useState([]);
|
||||
const [PacientesComRelatorios, setPacientesComRelatorios] = useState([]);
|
||||
const [MedicosComRelatorios, setMedicosComRelatorios] = useState([]);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [index, setIndex] = useState();
|
||||
|
||||
const ListarPacientes = async () => {
|
||||
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
|
||||
// busca lista de relatórios
|
||||
useEffect(() => {
|
||||
const fetchReports = async () => {
|
||||
try {
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("apikey", API_KEY);
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
headers: myHeaders,
|
||||
redirect: 'follow'
|
||||
};
|
||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&status", requestOptions)
|
||||
.then(response => response.json())
|
||||
.then(data => { setRelatorios(data); console.log(data) })
|
||||
.catch(error => console.log('error', error));
|
||||
}, [authHeader])
|
||||
const BaixarPDFdoRelatorio = (nome_paciente) => {
|
||||
const elemento = document.getElementById("folhaA4"); // tua div do relatório
|
||||
const opt = {
|
||||
margin: 0,
|
||||
filename: `relatorio_${nome_paciente || "paciente"}.pdf`,
|
||||
html2canvas: { scale: 2 },
|
||||
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
|
||||
};
|
||||
html2pdf().set(opt).from(elemento).save();
|
||||
myHeaders.append('apikey', API_KEY);
|
||||
myHeaders.append('Authorization', authHeader);
|
||||
var requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
|
||||
|
||||
const res = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*", requestOptions);
|
||||
const data = await res.json();
|
||||
setRelatorios(data || []);
|
||||
} catch (err) {
|
||||
console.error('Erro listar relatórios', err);
|
||||
setRelatorios([]);
|
||||
}
|
||||
};
|
||||
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 (
|
||||
<div>
|
||||
{showModal && (
|
||||
<div className="modal" >
|
||||
<div className="modal">
|
||||
<div className="modal-dialog modal-tabela-relatorio">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header text-white">
|
||||
<h5 className="modal-title ">Relatório de {PacientesComRelatorios[index]?.full_name} </h5>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
onClick={() => setShowModal(false)}
|
||||
></button>
|
||||
<h5 className="modal-title ">Relatório de {PacientesComRelatorios[index]?.full_name}</h5>
|
||||
<button type="button" className="btn-close" onClick={() => setShowModal(false)}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<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>Avenida - (79) 9 4444-4444</p>
|
||||
</div>
|
||||
|
||||
<div id='infoPaciente'>
|
||||
<p>Paciente: {PacientesComRelatorios[index]?.full_name}</p>
|
||||
<p>Data de nascimento: {PacientesComRelatorios[index]?.birth_date} </p>
|
||||
<p>Data do exame: {}</p>
|
||||
<p>Exame: {RelatoriosFiltrados[index]?.exam}</p>
|
||||
{/* INÍCIO DA MUDANÇA (da resposta anterior) */}
|
||||
<p>Data de nascimento: {PacientesComRelatorios[index]?.birth_date}</p>
|
||||
<p>Data do exame: {RelatoriosFiltrados[index]?.due_at || ''}</p>
|
||||
{/* Exibe conteúdo salvo (content_html) */}
|
||||
<p style={{ marginTop: '15px', fontWeight: 'bold' }}>Conteúdo do Relatório:</p>
|
||||
<TiptapViewer
|
||||
htmlContent={
|
||||
RelatoriosFiltrados[index]?.content ||
|
||||
RelatoriosFiltrados[index]?.diagnosis ||
|
||||
RelatoriosFiltrados[index]?.conclusion ||
|
||||
'Relatório não preenchido.'
|
||||
}
|
||||
/>
|
||||
{/* FIM DA MUDANÇA */}
|
||||
<TiptapViewer htmlContent={RelatoriosFiltrados[index]?.content_html || RelatoriosFiltrados[index]?.content || 'Relatório não preenchido.'} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>Dr {RelatoriosFiltrados[index]?.required_by}</p>
|
||||
<p>Emitido em: 0</p>
|
||||
<p>Dr {MedicosComRelatorios[index]?.full_name || RelatoriosFiltrados[index]?.requested_by}</p>
|
||||
<p>Emitido em: {RelatoriosFiltrados[index]?.created_at || '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={() => {setShowModal(false)}}
|
||||
>
|
||||
Fechar
|
||||
</button>
|
||||
<button type="button" className="btn btn-primary" onClick={() => { setShowModal(false) }}>Fechar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="page-heading">
|
||||
<h3>Lista de Relatórios</h3>
|
||||
</div>
|
||||
|
||||
<div className="page-heading"><h3>Lista de Relatórios</h3></div>
|
||||
<div className="page-content">
|
||||
<section className="row">
|
||||
<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">
|
||||
<h4 className="card-title mb-0">Relatórios Cadastrados</h4>
|
||||
<Link to={'criar'}>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
>
|
||||
<i className="bi bi-plus-circle"></i> Adicionar Relatório
|
||||
</button>
|
||||
<button className="btn btn-primary"><i className="bi bi-plus-circle"></i> Adicionar Relatório</button>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="card p-3 mb-3">
|
||||
<h5 className="mb-3">
|
||||
<i className="bi bi-funnel-fill me-2 text-primary"></i>{" "}
|
||||
Filtros
|
||||
</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",
|
||||
}}
|
||||
/>
|
||||
<h5 className="mb-3"><i className="bi bi-funnel-fill me-2 text-primary"></i> Filtros</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 className="table-responsive">
|
||||
<table className="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th>Paciente</th>
|
||||
<th>CPF</th>
|
||||
<th>Exame</th>
|
||||
|
||||
<th>Doutor</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{RelatoriosFiltrados.length > 0 ? (
|
||||
RelatoriosFiltrados.map((relatorio, index) => (
|
||||
RelatoriosFiltrados.map((relatorio, idx) => (
|
||||
<tr key={relatorio.id}>
|
||||
|
||||
<td className='infos-paciente'>{PacientesComRelatorios[index]?.full_name}</td>
|
||||
<td className='infos-paciente'>{PacientesComRelatorios[index]?.cpf}</td>
|
||||
<td>{relatorio.exam}</td>
|
||||
|
||||
|
||||
<td className='infos-paciente'>{PacientesComRelatorios[idx]?.full_name}</td>
|
||||
<td className='infos-paciente'>{MedicosComRelatorios[idx]?.full_name || relatorio.requested_by || '-'}</td>
|
||||
<td>
|
||||
<div className="d-flex gap-2">
|
||||
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
style={{
|
||||
backgroundColor: "#E6F2FF",
|
||||
color: "#004085",
|
||||
}}
|
||||
onClick={() => {
|
||||
setShowModal(true); setIndex(index)
|
||||
}}
|
||||
>
|
||||
<button className="btn btn-sm" style={{ backgroundColor: "#E6F2FF", color: "#004085" }} onClick={() => { setShowModal(true); setIndex(idx); }}>
|
||||
<i className="bi bi-eye me-1"></i> Ver Detalhes
|
||||
</button>
|
||||
|
||||
|
||||
<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`)
|
||||
}}
|
||||
>
|
||||
<button className="btn btn-sm" style={{ backgroundColor: "#FFF3CD", color: "#856404" }} onClick={() => navigate(`/medico/relatorios/${relatorio.id}/edit`)}>
|
||||
<i className="bi bi-pencil me-1"></i> Editar
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="8" className="text-center">
|
||||
Nenhum paciente encontrado.
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td colSpan="8" className="text-center">Nenhum paciente encontrado.</td></tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default DoctorRelatorioManager
|
||||
);
|
||||
};
|
||||
|
||||
export default DoctorRelatorioManager;
|
||||
|
||||
@ -1,172 +1,153 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import API_KEY from '../components/utils/apiKeys'
|
||||
import { useAuth } from '../components/utils/AuthProvider'
|
||||
import TiptapEditor from '../PagesMedico/TiptapEditor'
|
||||
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient'
|
||||
// EditPageRelatorio.jsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import API_KEY from '../components/utils/apiKeys';
|
||||
import { useAuth } from '../components/utils/AuthProvider';
|
||||
import TiptapEditor from '../PagesMedico/TiptapEditor';
|
||||
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient';
|
||||
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor';
|
||||
|
||||
const EditPageRelatorio = () => {
|
||||
const params = useParams()
|
||||
const navigate = useNavigate()
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
let authHeader = getAuthorizationHeader()
|
||||
|
||||
const [relatorioData, setRelatorioData] = useState({
|
||||
patient_id: '',
|
||||
exam: '',
|
||||
// Mantemos apenas os campos necessários para o fetch, mas não para edição direta na UI
|
||||
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 params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { getAuthorizationHeader } = useAuth();
|
||||
const authHeader = getAuthorizationHeader();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [report, setReport] = useState(null);
|
||||
const [patient, setPatient] = useState(null);
|
||||
const [doctor, setDoctor] = useState(null);
|
||||
const [html, setHtml] = useState('');
|
||||
|
||||
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 `
|
||||
<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 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>Data do exame:</strong></p>
|
||||
<p><strong>Exame:</strong> ${exam}</p>
|
||||
<br>
|
||||
<p><strong>Conteúdo do Relatório:</strong></p>
|
||||
<p>1</p>
|
||||
<br>
|
||||
<p>Dr</p>
|
||||
<br/>
|
||||
<p style="font-weight:bold;">Diagnóstico:</p>
|
||||
<p>${r?.diagnosis || ''}</p>
|
||||
<br/>
|
||||
<p style="font-weight:bold;">Conclusão:</p>
|
||||
<p>${r?.conclusion || ''}</p>
|
||||
<br/>
|
||||
<p>Dr ${doctorName}</p>
|
||||
<p>Emitido em: 0</p>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
// Função que será chamada ao salvar
|
||||
const handleSave = () => {
|
||||
setLoading(true)
|
||||
var myHeaders = new Headers();
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const myHeaders = new Headers();
|
||||
myHeaders.append("apikey", API_KEY);
|
||||
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)
|
||||
const raw = JSON.stringify({
|
||||
content: relatorioData.content,
|
||||
// Você pode manter order_number ou removê-lo se não for editável
|
||||
order_number: relatorioData.order_number || 'REL-2025-4386'
|
||||
})
|
||||
// Pega relatório por id (supabase geralmente retorna array para ?id=eq.X)
|
||||
const resp = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${params.id}`, requestOptions);
|
||||
const data = await resp.json();
|
||||
const rep = Array.isArray(data) ? data[0] : data;
|
||||
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',
|
||||
headers: myHeaders,
|
||||
body: raw,
|
||||
redirect: 'follow'
|
||||
};
|
||||
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${RelatorioID}`, requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {
|
||||
console.log(result);
|
||||
body
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const txt = await res.text();
|
||||
console.error('Erro PATCH', res.status, txt);
|
||||
throw new Error('Erro na API');
|
||||
}
|
||||
|
||||
alert('Relatório atualizado com sucesso!');
|
||||
setLoading(false)
|
||||
// MANTIDO: Volta para a área de relatórios
|
||||
navigate('/medico/relatorios')
|
||||
})
|
||||
.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);
|
||||
navigate('/medico/relatorios');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('Erro ao salvar. Veja console.');
|
||||
} finally {
|
||||
setLoading(false)
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
fetchReportData()
|
||||
}, [RelatorioID, authHeader])
|
||||
};
|
||||
|
||||
// Função para atualizar o HTML do editor
|
||||
const handleEditorChange = (newHtml) => {
|
||||
setRelatorioData(prev => ({ ...prev, content: newHtml }))
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <div>Carregando...</div>
|
||||
}
|
||||
if (loading) return <div>Carregando...</div>;
|
||||
|
||||
return (
|
||||
<div className='container'>
|
||||
{/* MANTIDO: Título limpo */}
|
||||
<h3 className='mb-4'>Editar Relatório do Paciente: {patientData?.full_name}</h3>
|
||||
<h3 className='mb-4'>Editar Relatório do Paciente: {patient?.full_name || '...'}</h3>
|
||||
|
||||
{/* MUDANÇA: Removidos todos os inputs de texto avulsos */}
|
||||
|
||||
{/* Campo do Tiptap Editor */}
|
||||
<div className='mb-3'>
|
||||
{/* MUDANÇA: Título ajustado */}
|
||||
<h5 className='mb-2'>Conteúdo do Relatório</h5>
|
||||
<TiptapEditor
|
||||
content={relatorioData.content}
|
||||
onChange={handleEditorChange}
|
||||
/>
|
||||
<TiptapEditor content={html} onChange={(newHtml) => setHtml(newHtml)} />
|
||||
</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>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditPageRelatorio;
|
||||
|
||||
export default EditPageRelatorio
|
||||
@ -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 [DictInfo, setDictInfo] = useState({})
|
||||
const { getAuthorizationHeader } = useAuth();
|
||||
const authHeader = getAuthorizationHeader();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
let authHeader = getAuthorizationHeader()
|
||||
const [patients, setPatients] = useState([]);
|
||||
const [doctors, setDoctors] = useState([]);
|
||||
const [loadingPatients, setLoadingPatients] = useState(true);
|
||||
const [loadingDoctors, setLoadingDoctors] = useState(true);
|
||||
|
||||
const handleSave = (data) => {
|
||||
console.log("Relatório salvo:", data);
|
||||
// formulário
|
||||
const [form, setForm] = useState({
|
||||
patient_id: '',
|
||||
patient_name: '',
|
||||
patient_birth: '',
|
||||
doctor_id: '',
|
||||
doctor_name: '',
|
||||
contentHtml: '',
|
||||
});
|
||||
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("apikey", API_KEY);
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
// campos de busca (texto)
|
||||
const [patientQuery, setPatientQuery] = useState('');
|
||||
const [doctorQuery, setDoctorQuery] = useState('');
|
||||
|
||||
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',
|
||||
headers: myHeaders,
|
||||
body: raw,
|
||||
redirect: 'follow'
|
||||
};
|
||||
body,
|
||||
});
|
||||
|
||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports", requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.log('error', error));
|
||||
if (!res.ok) {
|
||||
const txt = await res.text();
|
||||
console.error('Erro POST criar relatório:', res.status, txt);
|
||||
// mostra mensagem mais útil
|
||||
return alert(`Erro ao criar relatório (ver console). Status ${res.status}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>Criar Novo Relatorio</h3>
|
||||
<FormRelatorio DictInfo={DictInfo} setDictInfo={setDictInfo} onSave={handleSave} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
alert('Relatório criado com sucesso!');
|
||||
navigate('/medico/relatorios');
|
||||
} catch (err) {
|
||||
console.error('Erro salvar relatório:', err);
|
||||
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;
|
||||
|
||||
@ -1,74 +1,55 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import Link from '@tiptap/extension-link';
|
||||
// Componente da barra de menu (Menu Bar)
|
||||
|
||||
// MenuBar simples
|
||||
const MenuBar = ({ editor }) => {
|
||||
if (!editor) {
|
||||
return null;
|
||||
}
|
||||
// Estilos simples para os botões. Você pode e deve estilizar melhor com CSS/Bootstrap.
|
||||
const buttonStyle = {
|
||||
marginRight: '4px',
|
||||
padding: '4px 8px',
|
||||
cursor: 'pointer',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: editor.isActive('bold') || editor.isActive('italic') ? '#ddd' : 'white',
|
||||
};
|
||||
if (!editor) return null;
|
||||
const btn = { marginRight: '6px', padding: '4px 8px', cursor: 'pointer', border: '1px solid #ccc', borderRadius: 4 };
|
||||
return (
|
||||
<div style={{ padding: '8px', borderBottom: '1px solid #ccc', display: 'flex', flexWrap: 'wrap' }}>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
disabled={!editor.can().chain().focus().toggleBold().run()}
|
||||
style={{ ...buttonStyle, fontWeight: 'bold' }}
|
||||
>
|
||||
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 style={{ padding: 8, borderBottom: '1px solid #e6e6e6', display: 'flex', flexWrap: 'wrap' }}>
|
||||
<button style={{ ...btn, fontWeight: 'bold' }} onClick={() => editor.chain().focus().toggleBold().run()}>B</button>
|
||||
<button style={{ ...btn }} onClick={() => editor.chain().focus().toggleItalic().run()}>I</button>
|
||||
<button style={{ ...btn }} onClick={() => editor.chain().focus().toggleBulletList().run()}>Lista</button>
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// Componente principal do Editor
|
||||
|
||||
const TiptapEditor = ({ content, onChange }) => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
// Desativa 'hardBreak' e 'blockquote' se não forem necessários para simplificar
|
||||
hardBreak: false,
|
||||
}),
|
||||
Link, // Adiciona suporte para links
|
||||
StarterKit.configure({ hardBreak: false }),
|
||||
Link,
|
||||
],
|
||||
content: content || '<p>Inicie o relatório aqui...</p>',
|
||||
onUpdate: ({ editor }) => {
|
||||
// Quando o conteúdo muda, chama a função onChange com o HTML
|
||||
onChange(editor.getHTML());
|
||||
onChange && 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 (
|
||||
<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} />
|
||||
<EditorContent editor={editor} style={{ minHeight: '300px', padding: '10px' }} />
|
||||
<EditorContent editor={editor} style={{ minHeight: 360, padding: 12, background: 'white' }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TiptapEditor;
|
||||
@ -3,15 +3,18 @@ import { useEffect, useMemo,useState } from 'react'
|
||||
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor'
|
||||
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient'
|
||||
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 [Medico, setMedico] = useState({})
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
|
||||
const authHeader = getAuthorizationHeader()
|
||||
|
||||
|
||||
|
||||
|
||||
const ids = useMemo(() => {
|
||||
return {
|
||||
doctor_id: consulta?.doctor_id,
|
||||
@ -50,6 +53,8 @@ const CardConsultaPaciente = ({consulta}) => {
|
||||
|
||||
console.log(horario)
|
||||
|
||||
const deleteConsulta = () => {}
|
||||
|
||||
return (
|
||||
<div class="card-consulta">
|
||||
<div class="horario-container">
|
||||
@ -59,10 +64,40 @@ const CardConsultaPaciente = ({consulta}) => {
|
||||
</span>
|
||||
</div>
|
||||
<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}
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
112
src/PagesPaciente/ConsultaEditPage.jsx
Normal file
112
src/PagesPaciente/ConsultaEditPage.jsx
Normal 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
|
||||
@ -6,9 +6,12 @@ import { useEffect, useState } from 'react'
|
||||
import API_KEY from '../components/utils/apiKeys'
|
||||
import { useAuth } from '../components/utils/AuthProvider'
|
||||
|
||||
const ConsultasPaciente = () => {
|
||||
const ConsultasPaciente = ({setConsulta}) => {
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
|
||||
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||
const [selectedID, setSelectedId] = useState("")
|
||||
let authHeader = getAuthorizationHeader()
|
||||
|
||||
const [consultas, setConsultas] = useState([])
|
||||
@ -56,16 +59,32 @@ const FiltrarAgendamentos = (agendamentos, id) => {
|
||||
}, [])
|
||||
|
||||
const navigate = useNavigate()
|
||||
/*
|
||||
const consultas = [
|
||||
{
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"doctor_id": "eaca4372-17bc-4905-9eff-7aeda46157b4",
|
||||
"patient_id": "3854866a-5476-48be-8313-77029ccdd7a7",
|
||||
"scheduled_at": "2019-08-24T14:15:22Z",
|
||||
"status": "string"
|
||||
|
||||
const deleteConsulta= (ID) => {
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
myHeaders.append('apikey', API_KEY)
|
||||
myHeaders.append("authorization", authHeader)
|
||||
|
||||
|
||||
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 (
|
||||
<div>
|
||||
@ -81,12 +100,52 @@ const FiltrarAgendamentos = (agendamentos, id) => {
|
||||
<h2>Seus proximos atendimentos</h2>
|
||||
|
||||
{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>
|
||||
|
||||
@ -5,12 +5,13 @@ import { GetPatientByCPF } from "../components/utils/Functions-Endpoints/Patient
|
||||
import { GetDoctorByName, GetAllDoctors } from "../components/utils/Functions-Endpoints/Doctor";
|
||||
import { useAuth } from "../components/utils/AuthProvider";
|
||||
import API_KEY from "../components/utils/apiKeys";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
const FormConsultaPaciente = ({ onCancel, onSave, setAgendamento, agendamento }) => {
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
|
||||
console.log(agendamento, 'aqui2')
|
||||
console.log(agendamento?.dataAtendimento, 'aqui2')
|
||||
|
||||
const navigate = useNavigate()
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [anexos, setAnexos] = useState([]);
|
||||
const [loadingAnexos, setLoadingAnexos] = useState(false);
|
||||
@ -168,6 +169,7 @@ const formatarHora = (datetimeString) => {
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
alert("Agendamento salvo!");
|
||||
navigate("/paciente/agendamento")
|
||||
onSave({...agendamento, horarioInicio:horarioInicio})
|
||||
};
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
border-radius: 10px; /* Cantos arredondados */
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Sombra suave */
|
||||
overflow: hidden; /* Garante que o fundo azul não 'vaze' */
|
||||
width: 280px; /* Largura de exemplo */
|
||||
/* width: 280px; /* Largura de exemplo */
|
||||
margin: 20px;
|
||||
font-family: Arial, sans-serif; /* Fonte legível */
|
||||
}
|
||||
@ -37,8 +37,70 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 1; /* Faz com que a div de informações preencha o espaço restante */
|
||||
|
||||
gap:6rem;
|
||||
|
||||
}
|
||||
|
||||
.informacao {
|
||||
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 */
|
||||
}
|
||||
|
||||
@ -7,8 +7,6 @@ import "./style/card-consulta.css"
|
||||
const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, setDictInfo, setSelectedId} ) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
console.log(DadosConsulta, "AQUIIII")
|
||||
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
const authHeader = getAuthorizationHeader()
|
||||
const [Paciente, setPaciente] = useState()
|
||||
|
||||
@ -11,6 +11,9 @@ const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) =>
|
||||
|
||||
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 [anexos, setAnexos] = useState([]);
|
||||
const [loadingAnexos, setLoadingAnexos] = useState(false);
|
||||
@ -165,6 +168,33 @@ const formatarHora = (datetimeString) => {
|
||||
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) => {
|
||||
e.preventDefault();
|
||||
alert("Agendamento salvo!");
|
||||
@ -247,27 +277,12 @@ const handleSubmit = (e) => {
|
||||
</select>
|
||||
</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>
|
||||
|
||||
<section id="informacoes-atendimento-segunda-linha">
|
||||
<section id="informacoes-atendimento-segunda-linha-esquerda">
|
||||
|
||||
<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">
|
||||
@ -275,9 +290,8 @@ const handleSubmit = (e) => {
|
||||
<input type="date" name="dataAtendimento" onChange={handleChange} required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="row">
|
||||
<div className="linha">
|
||||
{/* Dropdown de Início (Não modificado) */}
|
||||
<div className="campo-de-input">
|
||||
<label htmlFor="inicio">Início *</label>
|
||||
<select
|
||||
@ -301,38 +315,44 @@ const handleSubmit = (e) => {
|
||||
</select>
|
||||
</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>
|
||||
|
||||
|
||||
|
||||
{/* Dropdown de Término */}
|
||||
<div className="campo-de-input">
|
||||
<label htmlFor="termino">Término *</label>
|
||||
<select
|
||||
<input
|
||||
type="text"
|
||||
id="termino"
|
||||
name="termino"
|
||||
required
|
||||
value={horarioTermino}
|
||||
onChange={(e) => setHorarioTermino(e.target.value)}
|
||||
>
|
||||
<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>
|
||||
value={horarioTermino || '— —'}
|
||||
readOnly
|
||||
className="horario-termino-readonly"
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
@ -344,13 +364,21 @@ const handleSubmit = (e) => {
|
||||
<textarea name="observacoes" rows="4" cols="1"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<div className="form-actions">
|
||||
<button type="submit" className="btn-primary">Salvar agendamento</button>
|
||||
<button type="button" className="btn-cancel" onClick={onCancel}>Cancelar</button>
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
|
||||
@ -19,6 +19,28 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
|
||||
setDia(ListaDiasComAgendamentos[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 (
|
||||
<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>
|
||||
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -67,15 +67,16 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteM
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
console.log(semanas)
|
||||
return semanas
|
||||
}, [agendamentos, AnoAtual]) // Adicionei AnoAtual como dependência por segurança
|
||||
}, [agendamentos, AnoAtual])
|
||||
|
||||
// --- EFEITO PARA POPULAR O ESTADO ---
|
||||
|
||||
useEffect(() => {
|
||||
setSemanasOrganizadas(OrganizarAgendamentosSemanais);
|
||||
// NOTA: Ao carregar, o Indice é 0, que é a primeira semana.
|
||||
|
||||
//console.log(semanasOrganizadas, `aqui`)
|
||||
}, [OrganizarAgendamentosSemanais])
|
||||
|
||||
// --- NOVAS FUNÇÕES DE NAVEGAÇÃO ---
|
||||
@ -156,15 +157,28 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteM
|
||||
</tr>
|
||||
</thead>
|
||||
<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}>
|
||||
{/* 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) */}
|
||||
<td>
|
||||
{semanaParaRenderizar.segunda[indiceLinha]
|
||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.segunda[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />
|
||||
{semanaParaRenderizar?.segunda[indiceLinha]
|
||||
? <CardConsulta DadosConsulta={semanaParaRenderizar?.segunda[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />
|
||||
: null
|
||||
}
|
||||
</td>
|
||||
@ -193,7 +207,7 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteM
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
)})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -110,6 +110,7 @@ svg{
|
||||
margin-top: 25px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@ -390,3 +391,84 @@ html[data-bs-theme="dark"] svg {
|
||||
background-position: center;
|
||||
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) */
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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 "./DoctorForm.css";
|
||||
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 }) {
|
||||
const navigate = useNavigate();
|
||||
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]
|
||||
);
|
||||
|
||||
const handleCepBlur = async () => {
|
||||
const cep = formData.cep?.replace(/\D/g, "");
|
||||
@ -205,6 +211,42 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
}
|
||||
}, 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 missingFields = [];
|
||||
@ -246,9 +288,23 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
|
||||
try {
|
||||
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 = () => {
|
||||
|
||||
@ -3,8 +3,8 @@ import { Clock } from "lucide-react";
|
||||
|
||||
const initialBlockTemplate = {
|
||||
id: null,
|
||||
inicio: "09:00",
|
||||
termino: "17:00",
|
||||
inicio: "07:00",
|
||||
termino: "18:00",
|
||||
isNew: true,
|
||||
};
|
||||
|
||||
@ -118,9 +118,9 @@ const HorariosDisponibilidade = ({
|
||||
flexDirection: window.innerWidth < 640 ? "column" : "row",
|
||||
alignItems: window.innerWidth < 640 ? "flex-start" : "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "16px",
|
||||
marginBottom: "16px",
|
||||
borderRadius: "12px",
|
||||
padding: "8px",
|
||||
marginBottom: "8px",
|
||||
borderRadius: "8px",
|
||||
boxShadow:
|
||||
"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
|
||||
transition: "all 0.3s",
|
||||
@ -132,7 +132,7 @@ const HorariosDisponibilidade = ({
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: window.innerWidth < 640 ? "column" : "row",
|
||||
gap: window.innerWidth < 640 ? "0" : "32px",
|
||||
gap: window.innerWidth < 640 ? "0" : "12px",
|
||||
width: window.innerWidth < 640 ? "100%" : "auto",
|
||||
}}
|
||||
>
|
||||
@ -140,7 +140,7 @@ const HorariosDisponibilidade = ({
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
gap: "4px",
|
||||
marginBottom: window.innerWidth < 640 ? "8px" : "0",
|
||||
}}
|
||||
>
|
||||
@ -159,20 +159,21 @@ const HorariosDisponibilidade = ({
|
||||
handleTimeChange(dayIndex, bloco.id, "inicio", e.target.value)
|
||||
}
|
||||
style={{
|
||||
padding: "8px",
|
||||
padding: "4px 6px",
|
||||
border: "1px solid #d1d5db",
|
||||
borderRadius: "8px",
|
||||
borderRadius: "6px",
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
outline: "none",
|
||||
fontSize: "13px"
|
||||
}}
|
||||
step="300"
|
||||
/>
|
||||
<Clock
|
||||
size={16}
|
||||
size={12}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: "12px",
|
||||
right: "8px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: "#9ca3af",
|
||||
@ -182,10 +183,10 @@ const HorariosDisponibilidade = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<label
|
||||
htmlFor={`termino-${dayIndex}-${bloco.id}`}
|
||||
style={{ fontWeight: 500, color: "#4b5563", width: "64px" }}
|
||||
style={{ fontWeight: 500, color: "#4b5563", width: "56px", fontSize: "13px" }}
|
||||
>
|
||||
Término:
|
||||
</label>
|
||||
@ -198,20 +199,21 @@ const HorariosDisponibilidade = ({
|
||||
handleTimeChange(dayIndex, bloco.id, "termino", e.target.value)
|
||||
}
|
||||
style={{
|
||||
padding: "8px",
|
||||
padding: "4px 6px",
|
||||
border: "1px solid #d1d5db",
|
||||
borderRadius: "8px",
|
||||
borderRadius: "6px",
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
outline: "none",
|
||||
fontSize: "13px",
|
||||
}}
|
||||
step="300"
|
||||
/>
|
||||
<Clock
|
||||
size={16}
|
||||
size={12}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: "12px",
|
||||
right: "8px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: "#9ca3af",
|
||||
@ -225,12 +227,12 @@ const HorariosDisponibilidade = ({
|
||||
<button
|
||||
onClick={() => handleRemoveBlock(dayIndex, bloco.id)}
|
||||
style={{
|
||||
marginTop: window.innerWidth < 640 ? "16px" : "0",
|
||||
padding: "8px 24px",
|
||||
marginTop: window.innerWidth < 640 ? "8px" : "0",
|
||||
padding: "4px 10px",
|
||||
backgroundColor: "#ef4444",
|
||||
color: "white",
|
||||
fontWeight: 600,
|
||||
borderRadius: "12px",
|
||||
borderRadius: "13px",
|
||||
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
||||
transition: "all 0.2s",
|
||||
width: window.innerWidth < 640 ? "100%" : "auto",
|
||||
@ -257,7 +259,6 @@ const HorariosDisponibilidade = ({
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
(Novo)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@ -295,8 +296,8 @@ const HorariosDisponibilidade = ({
|
||||
key={day.dia}
|
||||
style={{
|
||||
backgroundColor: "#f9fafb",
|
||||
padding: "20px",
|
||||
borderRadius: "12px",
|
||||
padding: "8px",
|
||||
borderRadius: "10px",
|
||||
border: "1px solid #e5e7eb",
|
||||
}}
|
||||
>
|
||||
@ -365,11 +366,11 @@ const HorariosDisponibilidade = ({
|
||||
<button
|
||||
onClick={() => handleAddBlock(dayIndex)}
|
||||
style={{
|
||||
marginTop: "24px",
|
||||
marginTop: "15px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "12px 24px",
|
||||
padding: "10px 22px",
|
||||
backgroundColor: "#10b981",
|
||||
color: "white",
|
||||
fontWeight: "bold",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
|
||||
import API_KEY from './apiKeys';
|
||||
// 1. Criação do Contexto
|
||||
const AuthContext = createContext(null);
|
||||
|
||||
@ -41,6 +41,8 @@ export function AuthProvider({ children }) {
|
||||
* @param {Object} tokenResponse O objeto completo retornado pelo Supabase Auth.
|
||||
*/
|
||||
const setAuthTokens = (tokenResponse) => {
|
||||
console.log("TOKEN ADICIONADO")
|
||||
|
||||
if (tokenResponse && tokenResponse.access_token && tokenResponse.token_type) {
|
||||
// 1. Atualiza o estado do React
|
||||
setAccessToken(tokenResponse.access_token);
|
||||
@ -77,6 +79,29 @@ export function AuthProvider({ children }) {
|
||||
|
||||
// --- 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 = {
|
||||
accessToken,
|
||||
tokenType,
|
||||
@ -85,8 +110,11 @@ export function AuthProvider({ children }) {
|
||||
setAuthTokens, // Usado para CRIAR/MUDAR (Login/Refresh)
|
||||
clearAuthTokens, // Usado para MUDAR (Logout)
|
||||
getAuthorizationHeader, // Usado para PEGAR (Endpoints)
|
||||
RefreshingToken
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={contextValue}>
|
||||
{children}
|
||||
|
||||
@ -1,25 +1,17 @@
|
||||
import API_KEY from "../apiKeys";
|
||||
|
||||
|
||||
|
||||
const GetDoctorByID = async (ID,authHeader) => {
|
||||
import API_KEY from '../apiKeys';
|
||||
|
||||
const GetDoctorByID = async (ID, authHeader) => {
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append('apikey', API_KEY)
|
||||
myHeaders.append('Authorization', authHeader)
|
||||
myHeaders.append('apikey', API_KEY);
|
||||
if (authHeader) myHeaders.append('Authorization', authHeader);
|
||||
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
headers:myHeaders
|
||||
const requestOptions = { method: 'GET', redirect: 'follow', headers: myHeaders };
|
||||
const res = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${ID}`, requestOptions);
|
||||
const DictMedico = await res.json();
|
||||
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) => {
|
||||
var myHeaders = new Headers();
|
||||
|
||||
@ -24,4 +24,9 @@ import API_KEY from "../apiKeys";
|
||||
return userInfoData
|
||||
}
|
||||
|
||||
const UploadFotoAvatar = ( userID,access_token,file) => {
|
||||
|
||||
|
||||
}
|
||||
|
||||
export {UserInfos}
|
||||
17
src/components/utils/fetchErros/CabecalhoError.jsx
Normal file
17
src/components/utils/fetchErros/CabecalhoError.jsx
Normal 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;
|
||||
15
src/components/utils/fetchErros/ManagerFunction.js
Normal file
15
src/components/utils/fetchErros/ManagerFunction.js
Normal 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
|
||||
64
src/components/utils/fetchErros/ModalErro.jsx
Normal file
64
src/components/utils/fetchErros/ModalErro.jsx
Normal 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
|
||||
26
src/components/utils/fetchErros/style.css
Normal file
26
src/components/utils/fetchErros/style.css
Normal 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;
|
||||
}
|
||||
@ -21,7 +21,7 @@
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Relaototios",
|
||||
"name": "Relatórios",
|
||||
"icon": "table",
|
||||
"url": "/admin/laudo"
|
||||
},
|
||||
|
||||
@ -21,60 +21,71 @@ import { Search } from 'lucide-react';
|
||||
|
||||
|
||||
|
||||
const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
|
||||
const Agendamento = ({setDictInfo}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Estados mesclados
|
||||
const [selectedID, setSelectedId] = useState('0') // (main)
|
||||
const [filaEsperaData, setfilaEsperaData] = useState([]) // (main)
|
||||
const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([])
|
||||
|
||||
const [selectedID, setSelectedId] = useState('0')
|
||||
const [filaEsperaData, setFilaEsperaData] = useState([])
|
||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||
const [tabela, setTabela] = useState('diario');
|
||||
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [agendamentos, setAgendamentos] = useState() // (main)
|
||||
const [agendamentos, setAgendamentos] = useState()
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({})
|
||||
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState() // (main)
|
||||
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState()
|
||||
|
||||
const [ListaDeMedicos, setListaDeMedicos] = useState([])
|
||||
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([])
|
||||
const [searchTermDoctor, setSearchTermDoctor] = useState('');
|
||||
|
||||
const [MedicoFiltrado, setMedicoFiltrado] = useState({id:"vazio"})
|
||||
|
||||
const [cacheFiladeEspera, setcacheFiladeEspera] = useState([])
|
||||
|
||||
const [cacheAgendamentos, setCacheAgendamentos] = useState([])
|
||||
|
||||
let authHeader = getAuthorizationHeader()
|
||||
|
||||
// Função FiltrarAgendamentos (Mesclado: Mantido o da MAIN, mais completo e com ordenação/fila de espera real)
|
||||
const FiltrarAgendamentos = async (listaTodosAgendamentos) => {
|
||||
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);
|
||||
const cacheMedicos = {};
|
||||
const cachePacientes = {};
|
||||
|
||||
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: {
|
||||
nome_medico: medico[0]?.full_name,
|
||||
doctor_id: medico[0]?.id,
|
||||
patient_id: paciente[0].id,
|
||||
paciente_nome: paciente[0].full_name,
|
||||
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);
|
||||
patient_id: paciente[0]?.id,
|
||||
paciente_nome: paciente[0]?.full_name,
|
||||
paciente_cpf: paciente[0]?.cpf,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
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) {
|
||||
DictAgendamentosOrganizados[DiaAgendamento].sort((a, b) => {
|
||||
if (a.scheduled_at < b.scheduled_at) return -1;
|
||||
if (a.scheduled_at > b.scheduled_at) return 1;
|
||||
return 0;
|
||||
});
|
||||
DictAgendamentosOrganizados[DiaAgendamento].sort((a, b) => a.scheduled_at.localeCompare(b.scheduled_at));
|
||||
}
|
||||
|
||||
const chavesOrdenadas = Object.keys(DictAgendamentosOrganizados).sort();
|
||||
|
||||
const chavesOrdenadas = Object.keys(DictAgendamentosOrganizados).sort((a, b) => {
|
||||
if (a < b) return -1;
|
||||
if (a > b) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
let DictAgendamentosFinal = {};
|
||||
const DictAgendamentosFinal = {};
|
||||
for (const data of chavesOrdenadas) {
|
||||
DictAgendamentosFinal[data] = DictAgendamentosOrganizados[data];
|
||||
}
|
||||
|
||||
setAgendamentosOrganizados(DictAgendamentosFinal);
|
||||
setfilaEsperaData(ListaFilaDeEspera);
|
||||
};
|
||||
setFilaEsperaData(ListaFilaDeEspera);
|
||||
};
|
||||
|
||||
fetchDados();
|
||||
|
||||
return { agendamentosOrganizados: DictAgendamentosOrganizados, filaEsperaData: ListaFilaDeEspera };
|
||||
}, [listaTodosAgendamentos]); // 👉 só recalcula quando a lista muda
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
var myHeaders = new Headers();
|
||||
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)
|
||||
.then(response => response.json())
|
||||
.then(result => {FiltrarAgendamentos(result);console.log(result)})
|
||||
.then(result => {setListaTodosAgendamentos(result);console.log(result)})
|
||||
.catch(error => console.log('error', error));
|
||||
|
||||
const PegarTodosOsMedicos = async () => {
|
||||
@ -135,56 +146,33 @@ const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
|
||||
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) => {
|
||||
console.log("tentando apagar")
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("apikey", API_KEY)
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
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)
|
||||
.then(response => response.json())
|
||||
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.${selectedPatientId}`, requestOptions)
|
||||
.then(response => {if(response.status !== 200)(console.log(response))})
|
||||
.then(result => console.log(result))
|
||||
.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(() => {
|
||||
@ -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) => {
|
||||
setSearchTermDoctor(term);
|
||||
if (term.trim() === '') {
|
||||
if(MedicoFiltrado.id !== "vazio"){
|
||||
console.log("Medico escolhido, mas vai ser apagado")
|
||||
console.log(cacheAgendamentos, "cache ")
|
||||
setAgendamentosOrganizados(cacheAgendamentos)
|
||||
}
|
||||
|
||||
|
||||
setFiltredTodosMedicos([]);
|
||||
setMedicoFiltrado({id:"vazio"})
|
||||
|
||||
//2 FiltrarAgendamentos()
|
||||
return;
|
||||
}
|
||||
if (FiltredTodosMedicos.length === 1){
|
||||
setMedicoFiltrado({...FiltredTodosMedicos[0]})
|
||||
}
|
||||
|
||||
const filtered = ListaDeMedicos.filter(medico =>
|
||||
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
||||
@ -247,8 +282,6 @@ const handleSearchMedicos = (term) => {
|
||||
};
|
||||
|
||||
|
||||
const handleClickCancel = () => setPageConsulta(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Agendar nova consulta</h1>
|
||||
@ -301,6 +334,9 @@ const handleSearchMedicos = (term) => {
|
||||
className='dropdown-item'
|
||||
onClick={() => {
|
||||
setSearchTermDoctor(medico.nomeMedico);
|
||||
setFiltredTodosMedicos([]);
|
||||
setMedicoFiltrado(medico)
|
||||
|
||||
}}
|
||||
>
|
||||
<p>{medico.nomeMedico} </p>
|
||||
@ -331,6 +367,7 @@ const handleSearchMedicos = (term) => {
|
||||
}}
|
||||
>
|
||||
Fila de espera
|
||||
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -432,7 +469,7 @@ const handleSearchMedicos = (term) => {
|
||||
<AgendamentoCadastroManager setPageConsulta={setPageConsulta} />
|
||||
)}
|
||||
|
||||
{/* Modal de Confirmação de Exclusão (Da MAIN) */}
|
||||
{/* Modal de Confirmação de Exclusão */}
|
||||
{showDeleteModal && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
|
||||
@ -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 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 [disponibilidades, setDisponibilidades] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [filtroMedicoId, setFiltroMedicoId] = useState("");
|
||||
const [filtroActive, setFiltroActive] = useState("true");
|
||||
const [medicoValido, setMedicoValido] = useState(false);
|
||||
const [filtroMedicoNome, setFiltroMedicoNome] = useState("");
|
||||
|
||||
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);
|
||||
let url = `${ENDPOINT_LISTAR}?select=*`;
|
||||
if (doctorId) url += `&doctor_id=eq.${doctorId}`;
|
||||
if (activeStatus === "true" || activeStatus === "false") url += `&active=eq.${activeStatus}`;
|
||||
setDisponibilidades([]);
|
||||
setMedicoEncontradoId(null);
|
||||
|
||||
const doctorId = encontrarMedicoIdPorNome(nome);
|
||||
|
||||
if (!doctorId) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `${ENDPOINT_LISTAR}?select=*&doctor_id=eq.${doctorId}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
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) {
|
||||
setDisponibilidades([]);
|
||||
setMedicoValido(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (filtroMedicoId) {
|
||||
fetchDisponibilidades(filtroMedicoId, filtroActive);
|
||||
if (filtroMedicoNome) {
|
||||
const timer = setTimeout(() => {
|
||||
fetchDisponibilidades(filtroMedicoNome);
|
||||
}, 300);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
} else {
|
||||
setDisponibilidades([]);
|
||||
setMedicoValido(false);
|
||||
setMedicoEncontradoId(null);
|
||||
}
|
||||
}, [filtroMedicoId, filtroActive, fetchDisponibilidades]);
|
||||
}, [filtroMedicoNome, fetchDisponibilidades]);
|
||||
|
||||
const rotaGerenciar = medicoEncontradoId
|
||||
? `../medicos/${medicoEncontradoId}/edit`
|
||||
: `../medicos/novo/edit`;
|
||||
|
||||
|
||||
return (
|
||||
<div id="main-content">
|
||||
@ -44,8 +100,9 @@ const DisponibilidadesDoctorPage = () => {
|
||||
<h1 style={{ fontSize: "1.5rem", fontWeight: "bold", color: "#333" }}>
|
||||
Disponibilidades por Médico
|
||||
</h1>
|
||||
|
||||
<Link
|
||||
to={medicoValido ? `../medicos/${filtroMedicoId}/edit` : "#"}
|
||||
to={rotaGerenciar}
|
||||
className="btn-primary"
|
||||
style={{
|
||||
padding: "10px 20px",
|
||||
@ -53,8 +110,6 @@ const DisponibilidadesDoctorPage = () => {
|
||||
whiteSpace: "nowrap",
|
||||
textDecoration: "none",
|
||||
display: "inline-block",
|
||||
opacity: medicoValido ? 1 : 0.6,
|
||||
pointerEvents: medicoValido ? "auto" : "none",
|
||||
}}
|
||||
>
|
||||
+ Gerenciar Disponibilidades
|
||||
@ -63,28 +118,16 @@ const DisponibilidadesDoctorPage = () => {
|
||||
|
||||
<div className="atendimento-eprocura">
|
||||
<div className="busca-atendimento">
|
||||
<div>
|
||||
<div style={{ marginRight: '10px' }}>
|
||||
<i className="fa-solid fa-user-doctor"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filtrar por ID do Médico..."
|
||||
value={filtroMedicoId}
|
||||
onChange={(e) => setFiltroMedicoId(e.target.value)}
|
||||
placeholder="Filtrar por Nome do Médico..."
|
||||
value={filtroMedicoNome}
|
||||
onChange={(e) => setFiltroMedicoNome(e.target.value)}
|
||||
style={{ border: "1px solid #ccc", borderRadius: "4px", padding: "5px" }}
|
||||
/>
|
||||
</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>
|
||||
|
||||
<section className="calendario-ou-filaespera">
|
||||
@ -95,15 +138,15 @@ const DisponibilidadesDoctorPage = () => {
|
||||
|
||||
{loading ? (
|
||||
<p className="text-center py-10">Carregando disponibilidades...</p>
|
||||
) : disponibilidades.length === 0 ? (
|
||||
) : (filtroMedicoNome && disponibilidades.length === 0) ? (
|
||||
<p className="text-center py-10">
|
||||
Nenhuma disponibilidade encontrada para os filtros aplicados.
|
||||
Nenhuma disponibilidade encontrada para o nome buscado.
|
||||
</p>
|
||||
) : (
|
||||
<table className="fila-tabela" style={{ width: "100%", borderCollapse: "collapse" }}>
|
||||
<thead>
|
||||
<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) => (
|
||||
<th
|
||||
key={header}
|
||||
@ -118,18 +161,17 @@ const DisponibilidadesDoctorPage = () => {
|
||||
<tbody>
|
||||
{disponibilidades.map((disp, index) => (
|
||||
<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" }}>{disp.doctor_id}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.weekday}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.start_time || "N/A"}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.end_time || "N/A"}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>
|
||||
{diasDaSemana[disp.weekday] || disp.weekday}
|
||||
</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>
|
||||
{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.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>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@ -9,12 +9,25 @@ function ForgotPassword() {
|
||||
setEmail(e.target.value);
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
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!");
|
||||
// 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 {
|
||||
setAlert("Preencha o campo de e-mail!");
|
||||
}
|
||||
|
||||
@ -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 { useAuth } from "../components/utils/AuthProvider";
|
||||
import API_KEY from "../components/utils/apiKeys";
|
||||
import { UserInfos } from "../components/utils/Functions-Endpoints/General";
|
||||
import CabecalhoError from "../components/utils/fetchErros/CabecalhoError";
|
||||
|
||||
function Login({ onEnterSystem }) {
|
||||
const { setAuthTokens } = useAuth();
|
||||
const [showCabecalho, setShowCabecalho ] = useState(false)
|
||||
const navigate = useNavigate();
|
||||
const [form, setForm] = useState({
|
||||
username: "",
|
||||
@ -126,6 +128,9 @@ function Login({ onEnterSystem }) {
|
||||
} else if (UserData?.roles?.includes("financeiro")) {
|
||||
navigate(`/financeiro/`);
|
||||
}
|
||||
}else{
|
||||
console.log("ERROROROROROOR")
|
||||
setShowCabecalho(true)
|
||||
}
|
||||
} else {
|
||||
setAlert("Preencha todos os campos!");
|
||||
@ -135,9 +140,9 @@ function Login({ onEnterSystem }) {
|
||||
return (
|
||||
<>
|
||||
<div className="mt-3 card-position">
|
||||
<div className="col-lg-5 col-md-7 col-sm-9 col-12 mx-auto">
|
||||
<div className="card shadow-sm d-flex justify-content-between align-items-center">
|
||||
<div id="auth-left" className="w-100">
|
||||
<div className="col-lg-5 col-md-7 col-sm-9 col-5 mx-auto">
|
||||
<div className="card w-10 shadow-sm d-flex justify-content-between align-items-center">
|
||||
<div id="auth-left" className="w-10">
|
||||
<div className="auth-logo">
|
||||
<br />
|
||||
<Link to="/">
|
||||
@ -148,11 +153,7 @@ function Login({ onEnterSystem }) {
|
||||
<p className="auth-subtitle mb-5">
|
||||
Entre com os dados que você inseriu durante o registro.
|
||||
</p>
|
||||
{alert && (
|
||||
<div className="alert alert-info" role="alert">
|
||||
{alert}
|
||||
</div>
|
||||
)}
|
||||
<CabecalhoError showCabecalho={showCabecalho} message={"E-mail ou senha incorretos."}/>
|
||||
<form onSubmit={handleLogin}>
|
||||
<div className="form-group position-relative has-icon-left mb-4">
|
||||
<input
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
|
||||
|
||||
function Register() {
|
||||
const navigate = useNavigate();
|
||||
const [form, setForm] = useState({
|
||||
@ -14,11 +15,12 @@ function Register() {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
|
||||
|
||||
const handleChange = (e) => {
|
||||
setForm({ ...form, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
const handleLogin = (e) => {
|
||||
const handleLogin = async (e) => {
|
||||
e.preventDefault();
|
||||
if (
|
||||
form.email &&
|
||||
@ -31,18 +33,98 @@ function Register() {
|
||||
setAlert("As senhas não coincidem!");
|
||||
return;
|
||||
}
|
||||
// ...register logic...
|
||||
navigate('/secretaria/inicio');
|
||||
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: 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 {
|
||||
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 (
|
||||
<>
|
||||
<div className="mt-3 card-position">
|
||||
<div className="col-lg-5 col-12">
|
||||
<div className="card shadow-sm d-flex justify-content-between align-items-center">
|
||||
<div className="col-lg-5 col-6">
|
||||
<div className="card w-10 shadow-sm d-flex justify-content-between align-items-center">
|
||||
<div id="auth-left">
|
||||
<div className="auth-logo">
|
||||
<br />
|
||||
@ -74,40 +156,8 @@ function Register() {
|
||||
<i className="bi bi-envelope" />
|
||||
</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">
|
||||
<input
|
||||
type={showPassword ? "text" : "password"}
|
||||
@ -179,4 +229,5 @@ function Register() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default Register;
|
||||
@ -3,10 +3,12 @@ import { Link } from "react-router-dom";
|
||||
import API_KEY from "../components/utils/apiKeys";
|
||||
import { useAuth } from "../components/utils/AuthProvider";
|
||||
import "./style/TablePaciente.css";
|
||||
import ModalErro from "../components/utils/fetchErros/ModalErro";
|
||||
import manager from "../components/utils/fetchErros/ManagerFunction";
|
||||
|
||||
function TablePaciente({ setCurrentPage, setPatientID }) {
|
||||
|
||||
const { getAuthorizationHeader, isAuthenticated } = useAuth();
|
||||
const { getAuthorizationHeader, isAuthenticated, RefreshingToken } = useAuth();
|
||||
|
||||
const [pacientes, setPacientes] = useState([]);
|
||||
const [search, setSearch] = useState("");
|
||||
@ -24,6 +26,10 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [selectedPatientId, setSelectedPatientId] = useState(null);
|
||||
|
||||
const [showModalError, setShowModalError] = useState("");
|
||||
|
||||
const [ ErrorInfo, setErrorInfo] = useState({})
|
||||
|
||||
const GetAnexos = async (id) => {
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Authorization", "Bearer <token>");
|
||||
@ -100,7 +106,6 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const authHeader = getAuthorizationHeader()
|
||||
|
||||
console.log(authHeader, 'aqui autorização')
|
||||
@ -114,10 +119,41 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
||||
redirect: 'follow'
|
||||
};
|
||||
|
||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions)
|
||||
.then(response => response.json())
|
||||
.then(result => setPacientes(result))
|
||||
.catch(error => console.log('error', error));
|
||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patient", requestOptions)
|
||||
.then(response => {
|
||||
|
||||
// 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]);
|
||||
|
||||
const ehAniversariante = (dataNascimento) => {
|
||||
@ -199,11 +235,12 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
||||
}) : [];
|
||||
|
||||
useEffect(() => {
|
||||
console.log(` Pacientes totais: ${pacientes.length}, Filtrados: ${pacientesFiltrados.length}`);
|
||||
console.log(` Pacientes totais: ${pacientes?.length}, Filtrados: ${pacientesFiltrados?.length}`);
|
||||
}, [pacientes, pacientesFiltrados, search]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalErro showModal={showModalError} setShowModal={setShowModalError} ErrorData={ErrorInfo}/>
|
||||
<div className="page-heading">
|
||||
<h3>Lista de Pacientes</h3>
|
||||
</div>
|
||||
@ -382,7 +419,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
||||
|
||||
<div className="mb-3">
|
||||
<span className="badge results-badge">
|
||||
{pacientesFiltrados.length} de {pacientes.length} pacientes encontrados
|
||||
{pacientesFiltrados?.length} de {pacientes?.length} pacientes encontrados
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,21 +1,27 @@
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import Sidebar from "../../components/Sidebar";
|
||||
import PacienteItems from "../../data/sidebar-items-paciente.json";
|
||||
|
||||
import { useState } from "react";
|
||||
import LaudoManager from "../../pages/LaudoManager";
|
||||
import ConsultaCadastroManager from "../../PagesPaciente/ConsultaCadastroManager";
|
||||
import ConsultasPaciente from "../../PagesPaciente/ConsultasPaciente";
|
||||
|
||||
import ConsultaEditPage from "../../PagesPaciente/ConsultaEditPage";
|
||||
function PerfilPaciente({ onLogout }) {
|
||||
|
||||
const [dadosConsulta, setConsulta] = useState({})
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<div id="app" className="active">
|
||||
<Sidebar onLogout={onLogout} menuItems={PacienteItems} />
|
||||
|
||||
<div id="main">
|
||||
<Routes>
|
||||
<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/edit" element={<ConsultaEditPage dadosConsulta={dadosConsulta} />} />
|
||||
<Route path="laudo" element={<LaudoManager />} />
|
||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||
</Routes>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user