Compare commits
23 Commits
8878ad6ced
...
a8fc3eb397
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8fc3eb397 | ||
|
|
95054bb9c1 | ||
|
|
844ebf03a8 | ||
|
|
b0306125ae | ||
| 38ebb5f7f3 | |||
| e4386c1776 | |||
| d80b13e404 | |||
| 272be628aa | |||
|
|
6d18a23dd7 | ||
|
|
1ba45e6c67 | ||
|
|
cf3ea901b8 | ||
|
|
aa399d2d99 | ||
|
|
191e997180 | ||
|
|
503a03e0b4 | ||
| ffa909734f | |||
| ba98884667 | |||
| d649d0ebc1 | |||
| ce3c5b8f22 | |||
|
|
c87bfbb4de | ||
|
|
96505d081a | ||
|
|
8f7fef3b8d | ||
|
|
edbb01e004 | ||
|
|
b07534bdc4 |
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/jest-dom": "^6.8.0",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@tiptap/core": "^3.7.2",
|
||||||
"@tiptap/extension-placeholder": "^3.7.1",
|
"@tiptap/extension-placeholder": "^3.7.1",
|
||||||
|
"@tiptap/pm": "^3.7.2",
|
||||||
"@tiptap/react": "^3.7.1",
|
"@tiptap/react": "^3.7.1",
|
||||||
"@tiptap/starter-kit": "^3.7.1",
|
"@tiptap/starter-kit": "^3.7.2",
|
||||||
"apexcharts": "^5.3.4",
|
"apexcharts": "^5.3.4",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.1",
|
||||||
|
|||||||
@ -1,226 +0,0 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
|
||||||
|
|
||||||
import TabelaAgendamentoDia from '../components/AgendarConsulta/TabelaAgendamentoDia';
|
|
||||||
import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana';
|
|
||||||
import TabelaAgendamentoMes from '../components/AgendarConsulta/TabelaAgendamentoMes';
|
|
||||||
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta';
|
|
||||||
|
|
||||||
// ✨ NOVO: Caminho de importação corrigido com base na sua estrutura de pastas
|
|
||||||
import AgendamentosMes from '../components/AgendarConsulta/DadosConsultasMock.js';
|
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
// Importe os estilos
|
|
||||||
import "./styleMedico/Agendamento.css";
|
|
||||||
import "./styleMedico/FilaEspera.css";
|
|
||||||
|
|
||||||
const Agendamento = () => {
|
|
||||||
|
|
||||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
|
||||||
const [tabela, setTabela] = useState('diario');
|
|
||||||
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
|
||||||
|
|
||||||
// Dados da fila de espera (sem alteração)
|
|
||||||
const filaEsperaData = [
|
|
||||||
{ nome: 'Ricardo Pereira', email: 'ricardo.pereira@gmail.com', cpf: '444.777.666-55', telefone: '(79) 99123-4567', entrada: '25/09/2025 às 08:00' },
|
|
||||||
{ nome: 'Ana Costa', email: 'ana.costa@gmail.com', cpf: '321.654.987-00', telefone: '(79) 97777-3333', entrada: '25/09/2025 às 08:30' },
|
|
||||||
{ nome: 'Lucas Martins', email: 'lucas.martins@gmail.com', cpf: '777.666.555-33', telefone: '(79) 99654-3210', entrada: '25/09/2025 às 09:00' },
|
|
||||||
{ nome: 'João Souza', email: 'joao.souza@gmail.com', cpf: '987.654.321-00', telefone: '(79) 98888-2222', entrada: '25/09/2025 às 14:00' },
|
|
||||||
{ nome: 'Maria Silva', email: 'maria.silva@gmail.com', cpf: '123.456.789-00', telefone: '(79) 99999-1111', entrada: '25/09/2025 às 14:30' },
|
|
||||||
{ nome: 'Fernanda Lima', email: 'fernanda.lima@gmail.com', cpf: '888.999.000-22', telefone: '(79) 98877-6655', entrada: '26/09/2025 às 09:30' },
|
|
||||||
{ nome: 'Carlos Andrade', email: 'carlos.andrade@gmail.com', cpf: '222.555.888-11', telefone: '(79) 99876-5432', entrada: '26/09/2025 às 10:00' },
|
|
||||||
{ nome: 'Juliana Oliveira', email: 'juliana.o@gmail.com', cpf: '111.222.333-44', telefone: '(79) 98765-1234', entrada: '26/09/2025 às 11:30' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Filtro da fila de espera (sem alteração)
|
|
||||||
const filteredFila = filaEsperaData.filter(item =>
|
|
||||||
item.nome.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
item.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
item.cpf.includes(searchTerm) ||
|
|
||||||
item.telefone.includes(searchTerm)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Lógica para filtrar os dados da AGENDA (AgendamentosMes)
|
|
||||||
const filteredAgendamentos = useMemo(() => {
|
|
||||||
if (!searchTerm.trim()) {
|
|
||||||
return AgendamentosMes;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
|
||||||
const filteredData = {};
|
|
||||||
|
|
||||||
for (const semana in AgendamentosMes) {
|
|
||||||
filteredData[semana] = {};
|
|
||||||
for (const dia in AgendamentosMes[semana]) {
|
|
||||||
filteredData[semana][dia] = AgendamentosMes[semana][dia].filter(agendamento =>
|
|
||||||
agendamento.status === 'vazio' ||
|
|
||||||
(agendamento.paciente && agendamento.paciente.toLowerCase().includes(lowerCaseSearchTerm))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filteredData;
|
|
||||||
}, [searchTerm]);
|
|
||||||
|
|
||||||
const ListarDiasdoMes = (ano, mes) => {
|
|
||||||
let segundas = []; let tercas = []; let quartas = []; let quintas = []; let sextas = []
|
|
||||||
const base = dayjs(`${ano}-${mes}-01`)
|
|
||||||
const DiasnoMes = base.daysInMonth()
|
|
||||||
for (let d = 1; d <= DiasnoMes; d++) {
|
|
||||||
const data = dayjs(`${ano}-${mes}-${d}`)
|
|
||||||
const dia = data.format('dddd')
|
|
||||||
switch (dia) {
|
|
||||||
case 'Monday': segundas.push(d); break
|
|
||||||
case 'Tuesday': tercas.push(d); break
|
|
||||||
case 'Wednesday': quartas.push(d); break
|
|
||||||
case 'Thursday': quintas.push(d); break
|
|
||||||
case 'Friday': sextas.push(d); break
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let ListaDiasDatas = [segundas, tercas, quartas, quintas, sextas]
|
|
||||||
return ListaDiasDatas
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClickAgendamento = (agendamento) => {
|
|
||||||
if (agendamento.status !== 'vazio') return
|
|
||||||
else setPageConsulta(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClickCancel = () => setPageConsulta(false)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Agendar nova consulta</h1>
|
|
||||||
|
|
||||||
{!PageNovaConsulta ? (
|
|
||||||
<div className='atendimento-eprocura'>
|
|
||||||
<div className='busca-atendimento'>
|
|
||||||
<div>
|
|
||||||
<i className="fa-solid fa-calendar-day"></i>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Buscar atendimento por paciente..."
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<select>
|
|
||||||
<option value="" disabled selected>Agendar</option>
|
|
||||||
<option value="">Atendimento</option>
|
|
||||||
<option value="">Sessões</option>
|
|
||||||
<option value="">Urgência</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='unidade-selecionarprofissional'>
|
|
||||||
<select>
|
|
||||||
<option value="" disabled selected >Unidade</option>
|
|
||||||
<option value="">Unidade Central</option>
|
|
||||||
<option value="">Unidade Zona Norte</option>
|
|
||||||
<option value="">Unidade Zona Oeste</option>
|
|
||||||
</select>
|
|
||||||
<input type="text" placeholder='Selecionar profissional' />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='container-btns-agenda-fila_esepera'>
|
|
||||||
<button
|
|
||||||
className={`btn-agenda ${FiladeEspera === false ? "opc-agenda-ativo" : ""}`}
|
|
||||||
onClick={() => {
|
|
||||||
setFiladeEspera(false);
|
|
||||||
setSearchTerm('');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Agenda
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`btn-fila-espera ${FiladeEspera === true ? "opc-filaespera-ativo" : ""}`}
|
|
||||||
onClick={() => {
|
|
||||||
setFiladeEspera(true);
|
|
||||||
setSearchTerm('');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Fila de espera
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section className='calendario-ou-filaespera'>
|
|
||||||
{FiladeEspera === false ?
|
|
||||||
(
|
|
||||||
<div className='calendario'>
|
|
||||||
<div>
|
|
||||||
<section className='btns-e-legenda-container'>
|
|
||||||
<div>
|
|
||||||
<button className={`btn-selecionar-tabeladia ${tabela === "diario" ? "ativo" : ""}`} onClick={() => setTabela("diario")}>
|
|
||||||
<i className="fa-solid fa-calendar-day"></i> Dia
|
|
||||||
</button>
|
|
||||||
<button className={`btn-selecionar-tabelasemana ${tabela === 'semanal' ? 'ativo' : ""}`} onClick={() => setTabela("semanal")}>
|
|
||||||
<i className="fa-solid fa-calendar-day"></i> Semana
|
|
||||||
</button>
|
|
||||||
<button className={`btn-selecionar-tabelames ${tabela === 'mensal' ? 'ativo' : ''}`} onClick={() => setTabela("mensal")}>
|
|
||||||
<i className="fa-solid fa-calendar-day"></i> Mês
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className='legenda-tabela'>
|
|
||||||
<div className='legenda-item-realizado'><span>Realizado</span></div>
|
|
||||||
<div className='legenda-item-confirmado'><span>Confirmado</span></div>
|
|
||||||
<div className='legenda-item-agendado'><span>Agendado</span></div>
|
|
||||||
<div className='legenda-item-cancelado'><span>Cancelado</span></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{tabela === "diario" && <TabelaAgendamentoDia handleClickAgendamento={handleClickAgendamento} agendamentos={filteredAgendamentos} />}
|
|
||||||
{tabela === 'semanal' && <TabelaAgendamentoSemana agendamentos={filteredAgendamentos} />}
|
|
||||||
{tabela === 'mensal' && <TabelaAgendamentoMes ListarDiasdoMes={ListarDiasdoMes} aplicarCores={true} agendamentos={filteredAgendamentos} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
:
|
|
||||||
(
|
|
||||||
<div className="fila-container">
|
|
||||||
<div className="fila-header">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Pesquisar na fila de espera..."
|
|
||||||
className="busca-fila-espera"
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
|
||||||
/>
|
|
||||||
<h2 className="fila-titulo">Fila de Espera</h2>
|
|
||||||
</div>
|
|
||||||
<table className="fila-tabela">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Nome</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th>CPF</th>
|
|
||||||
<th>Telefone</th>
|
|
||||||
<th>Entrou na fila de espera</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{filteredFila.map((item, index) => (
|
|
||||||
<tr key={index}>
|
|
||||||
<td>{item.nome}</td>
|
|
||||||
<td>{item.email}</td>
|
|
||||||
<td>{item.cpf}</td>
|
|
||||||
<td>{item.telefone}</td>
|
|
||||||
<td>{item.entrada}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<FormNovaConsulta onCancel={handleClickCancel} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Agendamento;
|
|
||||||
520
src/PagesMedico/DoctorAgendamentoManager.jsx
Normal file
520
src/PagesMedico/DoctorAgendamentoManager.jsx
Normal file
@ -0,0 +1,520 @@
|
|||||||
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import API_KEY from '../components/utils/apiKeys.js';
|
||||||
|
import AgendamentoCadastroManager from '../pages/AgendamentoCadastroManager.jsx';
|
||||||
|
import TabelaAgendamentoDia from '../components/AgendarConsulta/TabelaAgendamentoDia';
|
||||||
|
import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana';
|
||||||
|
import TabelaAgendamentoMes from '../components/AgendarConsulta/TabelaAgendamentoMes';
|
||||||
|
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta';
|
||||||
|
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient.js';
|
||||||
|
import { GetAllDoctors, GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor.js';
|
||||||
|
|
||||||
|
import { useAuth } from '../components/utils/AuthProvider.js';
|
||||||
|
// ✨ NOVO: Caminho de importação corrigido com base na sua estrutura de pastas
|
||||||
|
import AgendamentosMes from '../components/AgendarConsulta/DadosConsultasMock.js';
|
||||||
|
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import "../pages/style/Agendamento.css";
|
||||||
|
import '../pages/style/FilaEspera.css';
|
||||||
|
import { Search } from 'lucide-react';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const Agendamento = ({setDictInfo}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
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()
|
||||||
|
const {getAuthorizationHeader} = useAuth()
|
||||||
|
const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({})
|
||||||
|
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||||
|
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState()
|
||||||
|
|
||||||
|
const [ListaDeMedicos, setListaDeMedicos] = useState([])
|
||||||
|
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([])
|
||||||
|
const [searchTermDoctor, setSearchTermDoctor] = useState('');
|
||||||
|
|
||||||
|
|
||||||
|
let authHeader = getAuthorizationHeader()
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
let dicionario = {
|
||||||
|
agendamento: agendamento,
|
||||||
|
Infos: {
|
||||||
|
nome_nedico: medico.full_name,
|
||||||
|
doctor_id: medico.id,
|
||||||
|
patient_id: paciente[0].id,
|
||||||
|
paciente_nome: paciente[0].full_name,
|
||||||
|
paciente_cpf: paciente[0].cpf
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return dicionario;
|
||||||
|
};
|
||||||
|
|
||||||
|
let DictAgendamentosOrganizados = {};
|
||||||
|
let ListaFilaDeEspera = [];
|
||||||
|
|
||||||
|
// 1. Agrupamento (igual ao seu código original)
|
||||||
|
for (const agendamento of listaTodosAgendamentos) {
|
||||||
|
if (agendamento.status === 'requested') {
|
||||||
|
// Recomenda-se usar Promise.all para melhorar a performance
|
||||||
|
// mas, para manter a estrutura, mantemos o await no loop.
|
||||||
|
let v = await ConfigurarFiladeEspera(agendamento.patient_id, agendamento.doctor_id, agendamento);
|
||||||
|
ListaFilaDeEspera.push(v);
|
||||||
|
} else {
|
||||||
|
const DiaAgendamento = agendamento.scheduled_at.split("T")[0];
|
||||||
|
|
||||||
|
if (DiaAgendamento in DictAgendamentosOrganizados) {
|
||||||
|
DictAgendamentosOrganizados[DiaAgendamento].push(agendamento);
|
||||||
|
} else {
|
||||||
|
DictAgendamentosOrganizados[DiaAgendamento] = [agendamento];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// 2. Ordenação Interna: Ordenar os agendamentos por HORÁRIO (do menor para o maior)
|
||||||
|
for (const DiaAgendamento in DictAgendamentosOrganizados) {
|
||||||
|
DictAgendamentosOrganizados[DiaAgendamento].sort((a, b) => {
|
||||||
|
// Compara as strings de data/hora (ISO 8601) diretamente,
|
||||||
|
// que funcionam para ordenação cronológica.
|
||||||
|
if (a.scheduled_at < b.scheduled_at) return -1;
|
||||||
|
if (a.scheduled_at > b.scheduled_at) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// 3. Ordenação Externa: Ordenar os DIAS (as chaves do objeto)
|
||||||
|
// Para garantir que as chaves fiquem na sequência cronológica correta.
|
||||||
|
|
||||||
|
// Pega as chaves (datas)
|
||||||
|
const chavesOrdenadas = Object.keys(DictAgendamentosOrganizados).sort((a, b) => {
|
||||||
|
// Compara as chaves de data (strings 'YYYY-MM-DD')
|
||||||
|
if (a < b) return -1;
|
||||||
|
if (a > b) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cria um novo objeto no formato desejado, garantindo a ordem das chaves
|
||||||
|
let DictAgendamentosFinal = {};
|
||||||
|
for (const data of chavesOrdenadas) {
|
||||||
|
DictAgendamentosFinal[data] = DictAgendamentosOrganizados[data];
|
||||||
|
}
|
||||||
|
setAgendamentosOrganizados(DictAgendamentosFinal); // Use o objeto final ordenado
|
||||||
|
setfilaEsperaData(ListaFilaDeEspera);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Requisição inicial para mostrar os agendamentos do banco de dados
|
||||||
|
useEffect(() => {
|
||||||
|
var myHeaders = new Headers();
|
||||||
|
myHeaders.append("Authorization", authHeader);
|
||||||
|
myHeaders.append("apikey", API_KEY)
|
||||||
|
|
||||||
|
var requestOptions = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: myHeaders,
|
||||||
|
redirect: 'follow'
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(result => {FiltrarAgendamentos(result);})
|
||||||
|
.catch(error => console.log('error', error));
|
||||||
|
|
||||||
|
const PegarTodosOsMedicos = async () => {
|
||||||
|
let lista = []
|
||||||
|
const TodosOsMedicos = await GetAllDoctors(authHeader)
|
||||||
|
|
||||||
|
for(let d = 0; TodosOsMedicos.length > d; d++){
|
||||||
|
lista.push({nomeMedico: TodosOsMedicos[d].full_name, idMedico: TodosOsMedicos[d].id })}
|
||||||
|
setListaDeMedicos(lista)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
|
var requestOptions = {
|
||||||
|
method: 'DELETE',
|
||||||
|
redirect: 'follow',
|
||||||
|
headers: myHeaders
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${selectedPatientId}`, requestOptions)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(result => console.log(result))
|
||||||
|
.catch(error => console.log('error', error));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filtra todos os agendamentos em um objeto aninhado (data -> [agendamentos])
|
||||||
|
* com base no ID do médico.
|
||||||
|
*
|
||||||
|
* @param {Object} dictAgendamentos - O dicionário de agendamentos.
|
||||||
|
* @param {string} idMedicoFiltrado - O ID do médico (doctor_id) para ser usado como filtro.
|
||||||
|
* @returns {Array} Um array contendo todos os agendamentos que correspondem ao idMedicoFiltrado.
|
||||||
|
*/
|
||||||
|
const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
||||||
|
|
||||||
|
// O corpo da função deve usar esses nomes de variáveis:
|
||||||
|
const todasAsListasDeAgendamentos = Object.values(dictAgendamentos);
|
||||||
|
|
||||||
|
const todosOsAgendamentos = todasAsListasDeAgendamentos.flat();
|
||||||
|
|
||||||
|
const agendamentosFiltrados = todosOsAgendamentos.filter(agendamento =>
|
||||||
|
agendamento.doctor_id === idMedicoFiltrado
|
||||||
|
);
|
||||||
|
|
||||||
|
return agendamentosFiltrados;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Lógica para filtrar os dados da AGENDA (AgendamentosMes)
|
||||||
|
const filteredAgendamentos = useMemo(() => {
|
||||||
|
if (!searchTerm.trim()) {
|
||||||
|
return AgendamentosMes;
|
||||||
|
}
|
||||||
|
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
||||||
|
const filteredData = {};
|
||||||
|
|
||||||
|
for (const semana in AgendamentosMes) {
|
||||||
|
filteredData[semana] = {};
|
||||||
|
for (const dia in AgendamentosMes[semana]) {
|
||||||
|
filteredData[semana][dia] = AgendamentosMes[semana][dia].filter(agendamento =>
|
||||||
|
agendamento.status === 'vazio' ||
|
||||||
|
(agendamento.paciente && agendamento.paciente.toLowerCase().includes(lowerCaseSearchTerm))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredData;
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
const ListarDiasdoMes = (ano, mes) => {
|
||||||
|
let segundas = []; let tercas = []; let quartas = []; let quintas = []; let sextas = []
|
||||||
|
const base = dayjs(`${ano}-${mes}-01`)
|
||||||
|
const DiasnoMes = base.daysInMonth()
|
||||||
|
for (let d = 1; d <= DiasnoMes; d++) {
|
||||||
|
const data = dayjs(`${ano}-${mes}-${d}`)
|
||||||
|
const dia = data.format('dddd')
|
||||||
|
switch (dia) {
|
||||||
|
case 'Monday': segundas.push(d); break
|
||||||
|
case 'Tuesday': tercas.push(d); break
|
||||||
|
case 'Wednesday': quartas.push(d); break
|
||||||
|
case 'Thursday': quintas.push(d); break
|
||||||
|
case 'Friday': sextas.push(d); break
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ListaDiasDatas = {segundas:segundas,tercas:tercas,quartas: quartas,quintas: quintas,sextas: sextas}
|
||||||
|
return ListaDiasDatas
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickAgendamento = (agendamento) => {
|
||||||
|
if (agendamento.status !== 'vazio') return
|
||||||
|
else setPageConsulta(true)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleSearchMedicos = (term) => {
|
||||||
|
setSearchTermDoctor(term);
|
||||||
|
if (term.trim() === '') {
|
||||||
|
setFiltredTodosMedicos([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lógica simples de filtragem:
|
||||||
|
const filtered = ListaDeMedicos.filter(medico =>
|
||||||
|
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
||||||
|
);
|
||||||
|
setFiltredTodosMedicos(filtered);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleClickCancel = () => setPageConsulta(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Agendar nova consulta</h1>
|
||||||
|
|
||||||
|
<button className='manage-button btn' onClick={() => navigate('/secretaria/excecoes-disponibilidade')}>
|
||||||
|
<i className="bi bi-gear-fill me-1"></i>
|
||||||
|
Mudar Disponibilidade
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="btn btn-primary" onClick={() => setPageConsulta(true)}>
|
||||||
|
<i className="bi bi-plus-circle"></i> Adicionar Paciente
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{!PageNovaConsulta ? (
|
||||||
|
<div className='atendimento-eprocura'>
|
||||||
|
|
||||||
|
<div className='busca-atendimento-container'>
|
||||||
|
|
||||||
|
<div className='input-e-dropdown-wrapper'>
|
||||||
|
|
||||||
|
<div className='busca-atendimento'>
|
||||||
|
<div>
|
||||||
|
<i className="fa-solid fa-calendar-day"></i>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Filtrar atendimento por médico..."
|
||||||
|
value={searchTermDoctor}
|
||||||
|
onChange={(e) => handleSearchMedicos(e.target.value)} // Chama a nova função de filtro
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* DROPDOWN (RENDERIZAÇÃO CONDICIONAL) */}
|
||||||
|
{searchTermDoctor && FiltredTodosMedicos.length > 0 && (
|
||||||
|
<div className='dropdown-medicos'>
|
||||||
|
{FiltredTodosMedicos.map((medico) => (
|
||||||
|
<div
|
||||||
|
key={medico.id}
|
||||||
|
className='dropdown-item'
|
||||||
|
onClick={() => {
|
||||||
|
// Ação ao selecionar o médico
|
||||||
|
setSearchTermDoctor(medico.nomeMedico); // Preenche o input
|
||||||
|
//setFiltredTodosMedicos([]); // Fecha o dropdown
|
||||||
|
// Lógica adicional, como selecionar o ID do médico...
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>{medico.nomeMedico} </p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className='unidade-selecionarprofissional'>
|
||||||
|
<select>
|
||||||
|
<option value="" disabled selected >Unidade</option>
|
||||||
|
<option value="">Unidade Central</option>
|
||||||
|
<option value="">Unidade Zona Norte</option>
|
||||||
|
<option value="">Unidade Zona Oeste</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" placeholder='Selecionar profissional' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='container-btns-agenda-fila_esepera'>
|
||||||
|
<button
|
||||||
|
className={`btn-agenda ${FiladeEspera === false ? "opc-agenda-ativo" : ""}`}
|
||||||
|
onClick={() => {
|
||||||
|
setFiladeEspera(false);
|
||||||
|
setSearchTerm('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Agenda
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`btn-fila-espera ${FiladeEspera === true ? "opc-filaespera-ativo" : ""}`}
|
||||||
|
onClick={() => {
|
||||||
|
setFiladeEspera(true);
|
||||||
|
setSearchTerm('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Fila de espera
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section className='calendario-ou-filaespera'>
|
||||||
|
{FiladeEspera === false ?
|
||||||
|
(
|
||||||
|
<div className='calendario'>
|
||||||
|
<div>
|
||||||
|
<section className='btns-e-legenda-container'>
|
||||||
|
<div>
|
||||||
|
<button className={`btn-selecionar-tabeladia ${tabela === "diario" ? "ativo" : ""}`} onClick={() => setTabela("diario")}>
|
||||||
|
<i className="fa-solid fa-calendar-day"></i> Dia
|
||||||
|
</button>
|
||||||
|
<button className={`btn-selecionar-tabelasemana ${tabela === 'semanal' ? 'ativo' : ""}`} onClick={() => setTabela("semanal")}>
|
||||||
|
<i className="fa-solid fa-calendar-day"></i> Semana
|
||||||
|
</button>
|
||||||
|
<button className={`btn-selecionar-tabelames ${tabela === 'mensal' ? 'ativo' : ''}`} onClick={() => setTabela("mensal")}>
|
||||||
|
<i className="fa-solid fa-calendar-day"></i> Mês
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='legenda-tabela'>
|
||||||
|
<div className='legenda-item-realizado'><span>Realizado</span></div>
|
||||||
|
<div className='legenda-item-confirmado'><span>Confirmado</span></div>
|
||||||
|
<div className='legenda-item-agendado'><span>Agendado</span></div>
|
||||||
|
<div className='legenda-item-cancelado'><span>Cancelado</span></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{tabela === "diario" && <TabelaAgendamentoDia handleClickAgendamento={handleClickAgendamento} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />}
|
||||||
|
{tabela === 'semanal' && <TabelaAgendamentoSemana agendamentos={DictAgendamentosOrganizados} ListarDiasdoMes={ListarDiasdoMes} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>}
|
||||||
|
{tabela === 'mensal' && <TabelaAgendamentoMes ListarDiasdoMes={ListarDiasdoMes} aplicarCores={true} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
:
|
||||||
|
(
|
||||||
|
<div className="fila-container">
|
||||||
|
<div className="fila-header">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Pesquisar na fila de espera..."
|
||||||
|
className="busca-fila-espera"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
<h2 className="fila-titulo">Fila de Espera</h2>
|
||||||
|
</div>
|
||||||
|
<table className="fila-tabela">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Telefone</th>
|
||||||
|
|
||||||
|
<th>Telefone</th>
|
||||||
|
<th>Entrou na fila de espera</th>
|
||||||
|
<th>Ações</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{filaEsperaData.map((item, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td> <p>{item.Infos?.paciente_nome} </p> </td>
|
||||||
|
<td><p>{} </p></td>
|
||||||
|
<td>{}</td>
|
||||||
|
<td>{}</td>
|
||||||
|
<td> <div className="d-flex gap-2">
|
||||||
|
|
||||||
|
<button className="btn btn-sm btn-edit"
|
||||||
|
onClick={() => {
|
||||||
|
console.log(item, 'item')
|
||||||
|
navigate(`${2}/edit`)
|
||||||
|
setDictInfo(item)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="bi bi-pencil me-1"></i> Editar
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-delete"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.agendamento.id)
|
||||||
|
setShowDeleteModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="bi bi-trash me-1"></i> Excluir
|
||||||
|
</button>
|
||||||
|
</div></td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<AgendamentoCadastroManager setPageConsulta={setPageConsulta} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showDeleteModal && (
|
||||||
|
<div
|
||||||
|
className="modal fade show"
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||||
|
}}
|
||||||
|
tabIndex="-1"
|
||||||
|
onClick={(e) =>
|
||||||
|
e.target.classList.contains("modal") && setShowDeleteModal(false)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Agendamento;
|
||||||
@ -1,69 +1,88 @@
|
|||||||
|
// src/PagesMedico/DoctorRelatorioManager.jsx
|
||||||
import API_KEY from '../components/utils/apiKeys';
|
import API_KEY from '../components/utils/apiKeys';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import {useState, useEffect} from 'react'
|
import { useState, useEffect } from 'react';
|
||||||
import { useAuth } from '../components/utils/AuthProvider';
|
import { useAuth } from '../components/utils/AuthProvider';
|
||||||
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient';
|
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient';
|
||||||
|
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import html2pdf from 'html2pdf.js';
|
import html2pdf from 'html2pdf.js';
|
||||||
import TiptapViewer from './TiptapViewer';
|
import TiptapViewer from './TiptapViewer';
|
||||||
|
|
||||||
const DoctorRelatorioManager = () => {
|
const DoctorRelatorioManager = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
const { getAuthorizationHeader } = useAuth();
|
const { getAuthorizationHeader } = useAuth();
|
||||||
let authHeader = getAuthorizationHeader()
|
const authHeader = getAuthorizationHeader();
|
||||||
const [RelatoriosFiltrados, setRelatorios] = useState([])
|
const [RelatoriosFiltrados, setRelatorios] = useState([]);
|
||||||
const [PacientesComRelatorios, setPacientesComRelatorios] = useState([])
|
const [PacientesComRelatorios, setPacientesComRelatorios] = useState([]);
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [MedicosComRelatorios, setMedicosComRelatorios] = useState([]);
|
||||||
const [index, setIndex] = useState()
|
const [showModal, setShowModal] = useState(false);
|
||||||
// 1º useEffect: Busca os dados dos pacientes após carregar os relatórios
|
const [index, setIndex] = useState();
|
||||||
useEffect( () => {
|
|
||||||
let pacientesDosRelatorios = []
|
|
||||||
|
|
||||||
const ListarPacientes = async () => {
|
// busca lista de relatórios
|
||||||
for (let i = 0; i < RelatoriosFiltrados.length; i++) {
|
|
||||||
let relatorio = RelatoriosFiltrados[i];
|
|
||||||
let paciente_id = relatorio.patient_id;
|
|
||||||
const paciente = await GetPatientByID(paciente_id, authHeader);
|
|
||||||
console.log(paciente)
|
|
||||||
if (paciente.length > 0) {
|
|
||||||
pacientesDosRelatorios.push(paciente[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setPacientesComRelatorios(pacientesDosRelatorios);
|
|
||||||
}
|
|
||||||
ListarPacientes()
|
|
||||||
|
|
||||||
}, [RelatoriosFiltrados, authHeader]);
|
|
||||||
// NOVO: useEffect para logar PacientesComRelatorios após a atualização
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(PacientesComRelatorios, 'aqui')
|
|
||||||
}, [PacientesComRelatorios])
|
|
||||||
|
|
||||||
// 2º useEffect: Busca a lista de relatórios
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const fetchReports = async () => {
|
||||||
|
try {
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
myHeaders.append("apikey", API_KEY);
|
myHeaders.append('apikey', API_KEY);
|
||||||
myHeaders.append("Authorization", authHeader);
|
myHeaders.append('Authorization', authHeader);
|
||||||
var requestOptions = {
|
var requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
|
||||||
method: 'GET',
|
|
||||||
headers: myHeaders,
|
const res = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*", requestOptions);
|
||||||
redirect: 'follow'
|
const data = await res.json();
|
||||||
};
|
setRelatorios(data || []);
|
||||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&status", requestOptions)
|
} catch (err) {
|
||||||
.then(response => response.json())
|
console.error('Erro listar relatórios', err);
|
||||||
.then(data => { setRelatorios(data); console.log(data) })
|
setRelatorios([]);
|
||||||
.catch(error => console.log('error', error));
|
|
||||||
}, [authHeader])
|
|
||||||
const BaixarPDFdoRelatorio = (nome_paciente) => {
|
|
||||||
const elemento = document.getElementById("folhaA4"); // tua div do relatório
|
|
||||||
const opt = {
|
|
||||||
margin: 0,
|
|
||||||
filename: `relatorio_${nome_paciente || "paciente"}.pdf`,
|
|
||||||
html2canvas: { scale: 2 },
|
|
||||||
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
|
|
||||||
};
|
|
||||||
html2pdf().set(opt).from(elemento).save();
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
fetchReports();
|
||||||
|
}, [authHeader]);
|
||||||
|
|
||||||
|
// depois que RelatoriosFiltrados mudar, busca pacientes e médicos correspondentes
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchRelData = async () => {
|
||||||
|
const pacientes = [];
|
||||||
|
const medicos = [];
|
||||||
|
for (let i = 0; i < RelatoriosFiltrados.length; i++) {
|
||||||
|
const rel = RelatoriosFiltrados[i];
|
||||||
|
// paciente
|
||||||
|
try {
|
||||||
|
const pacienteRes = await GetPatientByID(rel.patient_id, authHeader);
|
||||||
|
pacientes.push(Array.isArray(pacienteRes) ? pacienteRes[0] : pacienteRes);
|
||||||
|
} catch (err) {
|
||||||
|
pacientes.push(null);
|
||||||
|
}
|
||||||
|
// médico: tenta created_by ou requested_by id se existir
|
||||||
|
try {
|
||||||
|
const doctorId = rel.created_by || rel.requested_by || null;
|
||||||
|
if (doctorId) {
|
||||||
|
// se created_by é id (uuid) usamos GetDoctorByID, senão se requested_by for nome, guardamos nome
|
||||||
|
const docRes = await GetDoctorByID(doctorId, authHeader);
|
||||||
|
medicos.push(Array.isArray(docRes) ? docRes[0] : docRes);
|
||||||
|
} else {
|
||||||
|
medicos.push({ full_name: rel.requested_by || '' });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
medicos.push({ full_name: rel.requested_by || '' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setPacientesComRelatorios(pacientes);
|
||||||
|
setMedicosComRelatorios(medicos);
|
||||||
|
};
|
||||||
|
if (RelatoriosFiltrados.length > 0) fetchRelData();
|
||||||
|
else {
|
||||||
|
setPacientesComRelatorios([]);
|
||||||
|
setMedicosComRelatorios([]);
|
||||||
|
}
|
||||||
|
}, [RelatoriosFiltrados, authHeader]);
|
||||||
|
|
||||||
|
const BaixarPDFdoRelatorio = (nome_paciente) => {
|
||||||
|
const elemento = document.getElementById("folhaA4");
|
||||||
|
const opt = { margin: 0, filename: `relatorio_${nome_paciente || "paciente"}.pdf`, html2canvas: { scale: 2 }, jsPDF: { unit: "mm", format: "a4", orientation: "portrait" } };
|
||||||
|
html2pdf().set(opt).from(elemento).save();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{showModal && (
|
{showModal && (
|
||||||
@ -72,11 +91,7 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu
|
|||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="modal-header text-white">
|
<div className="modal-header text-white">
|
||||||
<h5 className="modal-title ">Relatório de {PacientesComRelatorios[index]?.full_name}</h5>
|
<h5 className="modal-title ">Relatório de {PacientesComRelatorios[index]?.full_name}</h5>
|
||||||
<button
|
<button type="button" className="btn-close" onClick={() => setShowModal(false)}></button>
|
||||||
type="button"
|
|
||||||
className="btn-close"
|
|
||||||
onClick={() => setShowModal(false)}
|
|
||||||
></button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
<div id="folhaA4">
|
<div id="folhaA4">
|
||||||
@ -85,46 +100,32 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu
|
|||||||
<p>Dr - CRM/SP 123456</p>
|
<p>Dr - CRM/SP 123456</p>
|
||||||
<p>Avenida - (79) 9 4444-4444</p>
|
<p>Avenida - (79) 9 4444-4444</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id='infoPaciente'>
|
<div id='infoPaciente'>
|
||||||
<p>Paciente: {PacientesComRelatorios[index]?.full_name}</p>
|
<p>Paciente: {PacientesComRelatorios[index]?.full_name}</p>
|
||||||
<p>Data de nascimento: {PacientesComRelatorios[index]?.birth_date}</p>
|
<p>Data de nascimento: {PacientesComRelatorios[index]?.birth_date}</p>
|
||||||
<p>Data do exame: {}</p>
|
<p>Data do exame: {RelatoriosFiltrados[index]?.due_at || ''}</p>
|
||||||
<p>Exame: {RelatoriosFiltrados[index]?.exam}</p>
|
{/* Exibe conteúdo salvo (content_html) */}
|
||||||
{/* INÍCIO DA MUDANÇA (da resposta anterior) */}
|
|
||||||
<p style={{ marginTop: '15px', fontWeight: 'bold' }}>Conteúdo do Relatório:</p>
|
<p style={{ marginTop: '15px', fontWeight: 'bold' }}>Conteúdo do Relatório:</p>
|
||||||
<TiptapViewer
|
<TiptapViewer htmlContent={RelatoriosFiltrados[index]?.content_html || RelatoriosFiltrados[index]?.content || 'Relatório não preenchido.'} />
|
||||||
htmlContent={
|
|
||||||
RelatoriosFiltrados[index]?.content ||
|
|
||||||
RelatoriosFiltrados[index]?.diagnosis ||
|
|
||||||
RelatoriosFiltrados[index]?.conclusion ||
|
|
||||||
'Relatório não preenchido.'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{/* FIM DA MUDANÇA */}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p>Dr {RelatoriosFiltrados[index]?.required_by}</p>
|
<p>Dr {MedicosComRelatorios[index]?.full_name || RelatoriosFiltrados[index]?.requested_by}</p>
|
||||||
<p>Emitido em: 0</p>
|
<p>Emitido em: {RelatoriosFiltrados[index]?.created_at || '—'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
<button className="btn btn-primary" onClick={() => BaixarPDFdoRelatorio(PacientesComRelatorios[index]?.full_name)}><i className='bi bi-file-pdf-fill'></i> baixar em pdf</button>
|
<button className="btn btn-primary" onClick={() => BaixarPDFdoRelatorio(PacientesComRelatorios[index]?.full_name)}><i className='bi bi-file-pdf-fill'></i> baixar em pdf</button>
|
||||||
<button
|
<button type="button" className="btn btn-primary" onClick={() => { setShowModal(false) }}>Fechar</button>
|
||||||
type="button"
|
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={() => {setShowModal(false)}}
|
|
||||||
>
|
|
||||||
Fechar
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="page-heading">
|
|
||||||
<h3>Lista de Relatórios</h3>
|
<div className="page-heading"><h3>Lista de Relatórios</h3></div>
|
||||||
</div>
|
|
||||||
<div className="page-content">
|
<div className="page-content">
|
||||||
<section className="row">
|
<section className="row">
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
@ -132,114 +133,59 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu
|
|||||||
<div className="card-header d-flex justify-content-between align-items-center">
|
<div className="card-header d-flex justify-content-between align-items-center">
|
||||||
<h4 className="card-title mb-0">Relatórios Cadastrados</h4>
|
<h4 className="card-title mb-0">Relatórios Cadastrados</h4>
|
||||||
<Link to={'criar'}>
|
<Link to={'criar'}>
|
||||||
<button
|
<button className="btn btn-primary"><i className="bi bi-plus-circle"></i> Adicionar Relatório</button>
|
||||||
className="btn btn-primary"
|
|
||||||
>
|
|
||||||
<i className="bi bi-plus-circle"></i> Adicionar Relatório
|
|
||||||
</button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="card p-3 mb-3">
|
<div className="card p-3 mb-3">
|
||||||
<h5 className="mb-3">
|
<h5 className="mb-3"><i className="bi bi-funnel-fill me-2 text-primary"></i> Filtros</h5>
|
||||||
<i className="bi bi-funnel-fill me-2 text-primary"></i>{" "}
|
<div className="d-flex flex-nowrap align-items-center gap-2" style={{ overflowX: "auto", paddingBottom: "6px" }}>
|
||||||
Filtros
|
<input type="text" className="form-control" placeholder="Buscar por nome..." style={{ minWidth: 250, maxWidth: 300, width: 260, flex: "0 0 auto" }} />
|
||||||
</h5>
|
|
||||||
<div
|
|
||||||
className="d-flex flex-nowrap align-items-center gap-2"
|
|
||||||
style={{ overflowX: "auto", paddingBottom: "6px" }}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder="Buscar por nome..."
|
|
||||||
|
|
||||||
|
|
||||||
style={{
|
|
||||||
minWidth: 250,
|
|
||||||
maxWidth: 300,
|
|
||||||
width: 260,
|
|
||||||
flex: "0 0 auto",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<table className="table table-striped table-hover">
|
<table className="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<th>Paciente</th>
|
<th>Paciente</th>
|
||||||
<th>CPF</th>
|
<th>Doutor</th>
|
||||||
<th>Exame</th>
|
|
||||||
|
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{RelatoriosFiltrados.length > 0 ? (
|
{RelatoriosFiltrados.length > 0 ? (
|
||||||
RelatoriosFiltrados.map((relatorio, index) => (
|
RelatoriosFiltrados.map((relatorio, idx) => (
|
||||||
<tr key={relatorio.id}>
|
<tr key={relatorio.id}>
|
||||||
|
<td className='infos-paciente'>{PacientesComRelatorios[idx]?.full_name}</td>
|
||||||
<td className='infos-paciente'>{PacientesComRelatorios[index]?.full_name}</td>
|
<td className='infos-paciente'>{MedicosComRelatorios[idx]?.full_name || relatorio.requested_by || '-'}</td>
|
||||||
<td className='infos-paciente'>{PacientesComRelatorios[index]?.cpf}</td>
|
|
||||||
<td>{relatorio.exam}</td>
|
|
||||||
|
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<div className="d-flex gap-2">
|
<div className="d-flex gap-2">
|
||||||
|
<button className="btn btn-sm" style={{ backgroundColor: "#E6F2FF", color: "#004085" }} onClick={() => { setShowModal(true); setIndex(idx); }}>
|
||||||
<button
|
|
||||||
className="btn btn-sm"
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#E6F2FF",
|
|
||||||
color: "#004085",
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
setShowModal(true); setIndex(index)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i className="bi bi-eye me-1"></i> Ver Detalhes
|
<i className="bi bi-eye me-1"></i> Ver Detalhes
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button className="btn btn-sm" style={{ backgroundColor: "#FFF3CD", color: "#856404" }} onClick={() => navigate(`/medico/relatorios/${relatorio.id}/edit`)}>
|
||||||
<button
|
|
||||||
className="btn btn-sm"
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#FFF3CD",
|
|
||||||
color: "#856404",
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
|
|
||||||
// MANTIDO: Uso de string template para a navegação
|
|
||||||
navigate(`/medico/relatorios/${relatorio.id}/edit`)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i className="bi bi-pencil me-1"></i> Editar
|
<i className="bi bi-pencil me-1"></i> Editar
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr><td colSpan="8" className="text-center">Nenhum paciente encontrado.</td></tr>
|
||||||
<td colSpan="8" className="text-center">
|
|
||||||
Nenhum paciente encontrado.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
export default DoctorRelatorioManager
|
|
||||||
|
export default DoctorRelatorioManager;
|
||||||
|
|||||||
@ -1,172 +1,153 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
// EditPageRelatorio.jsx
|
||||||
import { useParams, useNavigate } from 'react-router-dom'
|
import React, { useEffect, useState } from 'react';
|
||||||
import API_KEY from '../components/utils/apiKeys'
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../components/utils/AuthProvider'
|
import API_KEY from '../components/utils/apiKeys';
|
||||||
import TiptapEditor from '../PagesMedico/TiptapEditor'
|
import { useAuth } from '../components/utils/AuthProvider';
|
||||||
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient'
|
import TiptapEditor from '../PagesMedico/TiptapEditor';
|
||||||
|
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient';
|
||||||
|
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor';
|
||||||
|
|
||||||
const EditPageRelatorio = () => {
|
const EditPageRelatorio = () => {
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const { getAuthorizationHeader } = useAuth();
|
||||||
let authHeader = getAuthorizationHeader()
|
const authHeader = getAuthorizationHeader();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const [relatorioData, setRelatorioData] = useState({
|
const [report, setReport] = useState(null);
|
||||||
patient_id: '',
|
const [patient, setPatient] = useState(null);
|
||||||
exam: '',
|
const [doctor, setDoctor] = useState(null);
|
||||||
// Mantemos apenas os campos necessários para o fetch, mas não para edição direta na UI
|
const [html, setHtml] = useState('');
|
||||||
required_by: '',
|
|
||||||
address_line: '',
|
|
||||||
content: '',
|
|
||||||
})
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [patientData, setPatientData] = useState(null) // Armazena dados do paciente
|
|
||||||
const RelatorioID = params.id
|
|
||||||
|
|
||||||
// Modelo HTML do relatório para ser carregado no Tiptap se o conteúdo for novo/vazio
|
|
||||||
const generateReportModel = (report, patient) => {
|
|
||||||
// Escapa as aspas se necessário, mas para HTML simples não é crucial
|
|
||||||
const patientName = patient?.full_name || 'Paciente não encontrado';
|
|
||||||
const birthDate = patient?.birth_date || 'Data não informada';
|
|
||||||
const exam = report?.exam || 'Exame não especificado';
|
|
||||||
|
|
||||||
|
const generateTemplate = (r = {}, p = {}, d = {}) => {
|
||||||
|
const patientName = p?.full_name || '[NOME DO PACIENTE]';
|
||||||
|
const birthDate = p?.birth_date || '';
|
||||||
|
const exam = r?.exam || '';
|
||||||
|
const doctorName = d?.full_name || r?.requested_by || '';
|
||||||
return `
|
return `
|
||||||
<div>
|
<div>
|
||||||
<p style="text-align:center; font-weight:bold;">Clinica Rise up</p>
|
<p style="text-align:center; font-weight:bold;">Clinica Rise up</p>
|
||||||
<p style="text-align:center;">Dr - CRM/SP 123456</p>
|
<p style="text-align:center;">Dr - CRM/SP 123456</p>
|
||||||
<p style="text-align:center;">Avenida - (79) 9 4444-4444</p>
|
<p style="text-align:center;">Avenida - (79) 9 4444-4444</p>
|
||||||
<br>
|
<br/>
|
||||||
<p><strong>Paciente:</strong> ${patientName}</p>
|
<p><strong>Paciente:</strong> ${patientName}</p>
|
||||||
<p><strong>Data de nascimento:</strong> ${birthDate}</p>
|
<p><strong>Data de nascimento:</strong> ${birthDate}</p>
|
||||||
<p><strong>Data do exame:</strong></p>
|
<p><strong>Data do exame:</strong></p>
|
||||||
<p><strong>Exame:</strong> ${exam}</p>
|
<p><strong>Exame:</strong> ${exam}</p>
|
||||||
<br>
|
<br/>
|
||||||
<p><strong>Conteúdo do Relatório:</strong></p>
|
<p style="font-weight:bold;">Diagnóstico:</p>
|
||||||
<p>1</p>
|
<p>${r?.diagnosis || ''}</p>
|
||||||
<br>
|
<br/>
|
||||||
<p>Dr</p>
|
<p style="font-weight:bold;">Conclusão:</p>
|
||||||
|
<p>${r?.conclusion || ''}</p>
|
||||||
|
<br/>
|
||||||
|
<p>Dr ${doctorName}</p>
|
||||||
<p>Emitido em: 0</p>
|
<p>Emitido em: 0</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Função que será chamada ao salvar
|
useEffect(() => {
|
||||||
const handleSave = () => {
|
const load = async () => {
|
||||||
setLoading(true)
|
setLoading(true);
|
||||||
var myHeaders = new Headers();
|
try {
|
||||||
|
const myHeaders = new Headers();
|
||||||
myHeaders.append("apikey", API_KEY);
|
myHeaders.append("apikey", API_KEY);
|
||||||
myHeaders.append("Authorization", authHeader);
|
myHeaders.append("Authorization", authHeader);
|
||||||
myHeaders.append("Content-Type", "application/json");
|
const requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
|
||||||
|
|
||||||
// Salva apenas o novo conteúdo do Tiptap (relatorioData.content)
|
// Pega relatório por id (supabase geralmente retorna array para ?id=eq.X)
|
||||||
const raw = JSON.stringify({
|
const resp = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${params.id}`, requestOptions);
|
||||||
content: relatorioData.content,
|
const data = await resp.json();
|
||||||
// Você pode manter order_number ou removê-lo se não for editável
|
const rep = Array.isArray(data) ? data[0] : data;
|
||||||
order_number: relatorioData.order_number || 'REL-2025-4386'
|
if (!rep) throw new Error('Relatório não encontrado');
|
||||||
})
|
|
||||||
|
|
||||||
var requestOptions = {
|
setReport(rep);
|
||||||
|
|
||||||
|
// busca paciente
|
||||||
|
if (rep.patient_id) {
|
||||||
|
const p = await GetPatientByID(rep.patient_id, authHeader);
|
||||||
|
setPatient(Array.isArray(p) ? p[0] : p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// busca doctor se tiver created_by/requested_by id (tentamos fallback)
|
||||||
|
if (rep.created_by) {
|
||||||
|
try {
|
||||||
|
const d = await GetDoctorByID(rep.created_by, authHeader);
|
||||||
|
setDoctor(Array.isArray(d) ? d[0] : d);
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// content_html preferencial
|
||||||
|
let initial = rep.content_html || rep.content || rep.diagnosis || rep.conclusion || '';
|
||||||
|
if (!initial || initial.trim() === '') {
|
||||||
|
initial = generateTemplate(rep, patient || {}, doctor || {});
|
||||||
|
}
|
||||||
|
setHtml(initial);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Erro carregar relatório', err);
|
||||||
|
alert('Erro ao carregar relatório. Veja console.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
load();
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [params.id, authHeader]);
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const myHeaders = new Headers();
|
||||||
|
myHeaders.append('apikey', API_KEY);
|
||||||
|
myHeaders.append('Authorization', authHeader);
|
||||||
|
myHeaders.append('Content-Type', 'application/json');
|
||||||
|
|
||||||
|
const body = JSON.stringify({ content_html: html });
|
||||||
|
|
||||||
|
// supabase: PATCH via query id=eq.<id>
|
||||||
|
const res = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${params.id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: myHeaders,
|
headers: myHeaders,
|
||||||
body: raw,
|
body
|
||||||
redirect: 'follow'
|
});
|
||||||
};
|
|
||||||
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${RelatorioID}`, requestOptions)
|
if (!res.ok) {
|
||||||
.then(response => response.text())
|
const txt = await res.text();
|
||||||
.then(result => {
|
console.error('Erro PATCH', res.status, txt);
|
||||||
console.log(result);
|
throw new Error('Erro na API');
|
||||||
|
}
|
||||||
|
|
||||||
alert('Relatório atualizado com sucesso!');
|
alert('Relatório atualizado com sucesso!');
|
||||||
setLoading(false)
|
navigate('/medico/relatorios');
|
||||||
// MANTIDO: Volta para a área de relatórios
|
} catch (err) {
|
||||||
navigate('/medico/relatorios')
|
console.error(err);
|
||||||
})
|
alert('Erro ao salvar. Veja console.');
|
||||||
.catch(error => {
|
|
||||||
console.log('error', error);
|
|
||||||
alert('Erro ao atualizar o relatório.');
|
|
||||||
setLoading(false)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Busca os dados do Relatório e do Paciente
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchReportData = async () => {
|
|
||||||
var myHeaders = new Headers();
|
|
||||||
myHeaders.append("apikey", API_KEY);
|
|
||||||
myHeaders.append("Authorization", authHeader);
|
|
||||||
var requestOptions = {
|
|
||||||
method: 'GET',
|
|
||||||
headers: myHeaders,
|
|
||||||
redirect: 'follow'
|
|
||||||
};
|
|
||||||
let report;
|
|
||||||
let patient;
|
|
||||||
try {
|
|
||||||
const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${RelatorioID}`, requestOptions);
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.length > 0) {
|
|
||||||
report = result[0];
|
|
||||||
|
|
||||||
// Busca nome do paciente
|
|
||||||
const patientResult = await GetPatientByID(report.patient_id, authHeader);
|
|
||||||
if (patientResult.length > 0) {
|
|
||||||
patient = patientResult[0];
|
|
||||||
setPatientData(patient);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determina o conteúdo inicial
|
|
||||||
let initialContent = report.content || report.diagnosis || report.conclusion || '';
|
|
||||||
|
|
||||||
// Se o conteúdo estiver vazio, carrega o modelo do relatório completo
|
|
||||||
if (!initialContent.trim()) {
|
|
||||||
initialContent = generateReportModel(report, patient);
|
|
||||||
}
|
|
||||||
|
|
||||||
setRelatorioData({
|
|
||||||
...report,
|
|
||||||
content: initialContent,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('error', error);
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
fetchReportData()
|
|
||||||
}, [RelatorioID, authHeader])
|
|
||||||
|
|
||||||
// Função para atualizar o HTML do editor
|
if (loading) return <div>Carregando...</div>;
|
||||||
const handleEditorChange = (newHtml) => {
|
|
||||||
setRelatorioData(prev => ({ ...prev, content: newHtml }))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <div>Carregando...</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='container'>
|
<div className='container'>
|
||||||
{/* MANTIDO: Título limpo */}
|
<h3 className='mb-4'>Editar Relatório do Paciente: {patient?.full_name || '...'}</h3>
|
||||||
<h3 className='mb-4'>Editar Relatório do Paciente: {patientData?.full_name}</h3>
|
|
||||||
|
|
||||||
{/* MUDANÇA: Removidos todos os inputs de texto avulsos */}
|
|
||||||
|
|
||||||
{/* Campo do Tiptap Editor */}
|
|
||||||
<div className='mb-3'>
|
<div className='mb-3'>
|
||||||
{/* MUDANÇA: Título ajustado */}
|
|
||||||
<h5 className='mb-2'>Conteúdo do Relatório</h5>
|
<h5 className='mb-2'>Conteúdo do Relatório</h5>
|
||||||
<TiptapEditor
|
<TiptapEditor content={html} onChange={(newHtml) => setHtml(newHtml)} />
|
||||||
content={relatorioData.content}
|
|
||||||
onChange={handleEditorChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<button className='btn btn-success' onClick={handleSave}>
|
|
||||||
Salvar Relatório
|
<div className='d-flex justify-content-center mt-4'>
|
||||||
|
<button className='btn btn-success' onClick={handleSave} disabled={loading}>
|
||||||
|
{loading ? 'Salvando...' : 'Salvar Relatório'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditPageRelatorio;
|
||||||
|
|
||||||
export default EditPageRelatorio
|
|
||||||
@ -1,44 +1,243 @@
|
|||||||
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import API_KEY from '../components/utils/apiKeys';
|
||||||
|
import { useAuth } from '../components/utils/AuthProvider';
|
||||||
|
import TiptapEditor from './TiptapEditor';
|
||||||
|
import { GetAllPatients, GetPatientByID } from '../components/utils/Functions-Endpoints/Patient';
|
||||||
|
import { GetAllDoctors, GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor';
|
||||||
|
import './styleMedico/FormNovoRelatorio.css';
|
||||||
|
|
||||||
import '../PagesMedico/styleMedico/FormNovoRelatorio.css'
|
|
||||||
import API_KEY from '../components/utils/apiKeys'
|
|
||||||
import FormRelatorio from '../components/FormRelatorio'
|
|
||||||
import { useState } from 'react'
|
|
||||||
import { useAuth } from '../components/utils/AuthProvider'
|
|
||||||
const FormNovoRelatorio = () => {
|
const FormNovoRelatorio = () => {
|
||||||
const [DictInfo, setDictInfo] = useState({})
|
const { getAuthorizationHeader } = useAuth();
|
||||||
|
const authHeader = getAuthorizationHeader();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const [patients, setPatients] = useState([]);
|
||||||
let authHeader = getAuthorizationHeader()
|
const [doctors, setDoctors] = useState([]);
|
||||||
|
const [loadingPatients, setLoadingPatients] = useState(true);
|
||||||
|
const [loadingDoctors, setLoadingDoctors] = useState(true);
|
||||||
|
|
||||||
const handleSave = (data) => {
|
// formulário
|
||||||
console.log("Relatório salvo:", data);
|
const [form, setForm] = useState({
|
||||||
|
patient_id: '',
|
||||||
|
patient_name: '',
|
||||||
|
patient_birth: '',
|
||||||
|
doctor_id: '',
|
||||||
|
doctor_name: '',
|
||||||
|
contentHtml: '',
|
||||||
|
});
|
||||||
|
|
||||||
var myHeaders = new Headers();
|
// campos de busca (texto)
|
||||||
myHeaders.append("apikey", API_KEY);
|
const [patientQuery, setPatientQuery] = useState('');
|
||||||
myHeaders.append("Authorization", authHeader);
|
const [doctorQuery, setDoctorQuery] = useState('');
|
||||||
myHeaders.append("Content-Type", "application/json");
|
|
||||||
|
|
||||||
var raw = JSON.stringify({...data});
|
// dropdown control
|
||||||
|
const [showPatientDropdown, setShowPatientDropdown] = useState(false);
|
||||||
|
const [showDoctorDropdown, setShowDoctorDropdown] = useState(false);
|
||||||
|
|
||||||
var requestOptions = {
|
const patientRef = useRef();
|
||||||
method: 'POST',
|
const doctorRef = useRef();
|
||||||
headers: myHeaders,
|
|
||||||
body: raw,
|
useEffect(() => {
|
||||||
redirect: 'follow'
|
// 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>
|
||||||
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports", requestOptions)
|
// escolher paciente (clicando na lista)
|
||||||
.then(response => response.text())
|
const choosePatient = async (patient) => {
|
||||||
.then(result => console.log(result))
|
setForm(prev => ({
|
||||||
.catch(error => console.log('error', error));
|
...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,
|
||||||
|
});
|
||||||
|
|
||||||
|
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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="container">
|
||||||
<h3>Criar Novo Relatorio</h3>
|
<h3 className="mb-4">Criar Novo Relatório</h3>
|
||||||
<FormRelatorio DictInfo={DictInfo} setDictInfo={setDictInfo} onSave={handleSave} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FormNovoRelatorio
|
<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 { useEditor, EditorContent } from '@tiptap/react';
|
||||||
import StarterKit from '@tiptap/starter-kit';
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
import Link from '@tiptap/extension-link';
|
import Link from '@tiptap/extension-link';
|
||||||
// Componente da barra de menu (Menu Bar)
|
|
||||||
|
// MenuBar simples
|
||||||
const MenuBar = ({ editor }) => {
|
const MenuBar = ({ editor }) => {
|
||||||
if (!editor) {
|
if (!editor) return null;
|
||||||
return null;
|
const btn = { marginRight: '6px', padding: '4px 8px', cursor: 'pointer', border: '1px solid #ccc', borderRadius: 4 };
|
||||||
}
|
|
||||||
// Estilos simples para os botões. Você pode e deve estilizar melhor com CSS/Bootstrap.
|
|
||||||
const buttonStyle = {
|
|
||||||
marginRight: '4px',
|
|
||||||
padding: '4px 8px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
border: '1px solid #ccc',
|
|
||||||
borderRadius: '4px',
|
|
||||||
backgroundColor: editor.isActive('bold') || editor.isActive('italic') ? '#ddd' : 'white',
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '8px', borderBottom: '1px solid #ccc', display: 'flex', flexWrap: 'wrap' }}>
|
<div style={{ padding: 8, borderBottom: '1px solid #e6e6e6', display: 'flex', flexWrap: 'wrap' }}>
|
||||||
<button
|
<button style={{ ...btn, fontWeight: 'bold' }} onClick={() => editor.chain().focus().toggleBold().run()}>B</button>
|
||||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
<button style={{ ...btn }} onClick={() => editor.chain().focus().toggleItalic().run()}>I</button>
|
||||||
disabled={!editor.can().chain().focus().toggleBold().run()}
|
<button style={{ ...btn }} onClick={() => editor.chain().focus().toggleBulletList().run()}>Lista</button>
|
||||||
style={{ ...buttonStyle, fontWeight: 'bold' }}
|
<button style={{ ...btn }} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}>Título 2</button>
|
||||||
>
|
<button style={{ ...btn }} onClick={() => { const url = prompt('URL'); if (url) editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run(); }}>Link</button>
|
||||||
B
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
|
||||||
disabled={!editor.can().chain().focus().toggleItalic().run()}
|
|
||||||
style={{ ...buttonStyle, fontStyle: 'italic' }}
|
|
||||||
>
|
|
||||||
I
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
|
||||||
style={{ ...buttonStyle, backgroundColor: editor.isActive('bulletList') ? '#ddd' : 'white' }}
|
|
||||||
>
|
|
||||||
Lista
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
|
||||||
style={{ ...buttonStyle, backgroundColor: editor.isActive('heading', { level: 2 }) ? '#ddd' : 'white' }}
|
|
||||||
>
|
|
||||||
Título 2
|
|
||||||
</button>
|
|
||||||
{/* Adicione mais botões conforme a necessidade (link, código, etc.) */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
// Componente principal do Editor
|
|
||||||
const TiptapEditor = ({ content, onChange }) => {
|
const TiptapEditor = ({ content, onChange }) => {
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
StarterKit.configure({
|
StarterKit.configure({ hardBreak: false }),
|
||||||
// Desativa 'hardBreak' e 'blockquote' se não forem necessários para simplificar
|
Link,
|
||||||
hardBreak: false,
|
|
||||||
}),
|
|
||||||
Link, // Adiciona suporte para links
|
|
||||||
],
|
],
|
||||||
content: content || '<p>Inicie o relatório aqui...</p>',
|
content: content || '<p>Inicie o relatório aqui...</p>',
|
||||||
onUpdate: ({ editor }) => {
|
onUpdate: ({ editor }) => {
|
||||||
// Quando o conteúdo muda, chama a função onChange com o HTML
|
onChange && onChange(editor.getHTML());
|
||||||
onChange(editor.getHTML());
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Se o pai mudar 'content', atualizamos o editor
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editor) return;
|
||||||
|
// Só setContent se for diferente para evitar perda de edição
|
||||||
|
try {
|
||||||
|
const current = editor.getHTML();
|
||||||
|
if ((content || '').trim() && content !== current) {
|
||||||
|
editor.commands.setContent(content);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}, [editor, content]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='tiptap-editor-container' style={{ border: '1px solid #ccc', borderRadius: '4px' }}>
|
<div className='tiptap-editor-container' style={{ border: '1px solid #ddd', borderRadius: 6, overflow: 'hidden' }}>
|
||||||
<MenuBar editor={editor} />
|
<MenuBar editor={editor} />
|
||||||
<EditorContent editor={editor} style={{ minHeight: '300px', padding: '10px' }} />
|
<EditorContent editor={editor} style={{ minHeight: 360, padding: 12, background: 'white' }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TiptapEditor;
|
export default TiptapEditor;
|
||||||
105
src/PagesPaciente/CardConsultaPaciente.jsx
Normal file
105
src/PagesPaciente/CardConsultaPaciente.jsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import React from 'react'
|
||||||
|
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 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,
|
||||||
|
patient_id: consulta?.patient_id,
|
||||||
|
status: consulta?.status
|
||||||
|
};
|
||||||
|
}, [consulta]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const BuscarMedicoEPaciente = async () => {
|
||||||
|
if (!ids.doctor_id || !ids.patient_id || ids.status === 'nada') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [Doctor, Patient] = await Promise.all([
|
||||||
|
GetDoctorByID(ids.doctor_id, authHeader),
|
||||||
|
GetPatientByID(ids.patient_id, authHeader)
|
||||||
|
]);
|
||||||
|
|
||||||
|
setMedico(Doctor?.[0] || null);
|
||||||
|
setPaciente(Patient?.[0] || null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao buscar médico/paciente:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BuscarMedicoEPaciente();
|
||||||
|
}, [ids, authHeader]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
console.log(consulta, "dento do card")
|
||||||
|
|
||||||
|
let horario = consulta.scheduled_at.split("T")[1]
|
||||||
|
let Data = consulta.scheduled_at.split("T")[0]
|
||||||
|
|
||||||
|
console.log(horario)
|
||||||
|
|
||||||
|
const deleteConsulta = () => {}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="card-consulta">
|
||||||
|
<div class="horario-container">
|
||||||
|
|
||||||
|
<span class="horario">
|
||||||
|
{`${Data?.split("-")[2]}/${Data?.split("-")[1]}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-container">
|
||||||
|
|
||||||
|
<p>{`Inicio: ${horario.split(":")[0]}:${horario.split(":")[1]}`}</p>
|
||||||
|
|
||||||
|
<p class="informacao">
|
||||||
|
|
||||||
|
Dr {Medico?.full_name} - {Medico?.specialty}
|
||||||
|
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CardConsultaPaciente
|
||||||
81
src/PagesPaciente/ConsultaCadastroManager.jsx
Normal file
81
src/PagesPaciente/ConsultaCadastroManager.jsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import FormConsultaPaciente from './FormConsultaPaciente'
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { useAuth } from '../components/utils/AuthProvider'
|
||||||
|
import API_KEY from '../components/utils/apiKeys'
|
||||||
|
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { UserInfos } from '../components/utils/Functions-Endpoints/General'
|
||||||
|
const ConsultaCadastroManager = () => {
|
||||||
|
|
||||||
|
const {getAuthorizationHeader} = useAuth()
|
||||||
|
const [Dict, setDict] = useState({})
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [idUsuario, setIDusuario] = useState("")
|
||||||
|
|
||||||
|
let authHeader = getAuthorizationHeader()
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ColherInfoUsuario =async () => {
|
||||||
|
const result = await UserInfos(authHeader)
|
||||||
|
|
||||||
|
setIDusuario(result?.profile?.id)
|
||||||
|
|
||||||
|
}
|
||||||
|
ColherInfoUsuario()
|
||||||
|
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleSave = (Dict) => {
|
||||||
|
let DataAtual = dayjs()
|
||||||
|
var myHeaders = new Headers();
|
||||||
|
myHeaders.append("apikey", API_KEY);
|
||||||
|
myHeaders.append("Authorization", authHeader);
|
||||||
|
myHeaders.append("Content-Type", "application/json");
|
||||||
|
|
||||||
|
var raw = JSON.stringify({
|
||||||
|
"patient_id": Dict.patient_id,
|
||||||
|
"doctor_id": Dict.doctor_id,
|
||||||
|
"scheduled_at": `${Dict.dataAtendimento}T${Dict.horarioInicio}:00.000Z`,
|
||||||
|
"duration_minutes": 30,
|
||||||
|
"appointment_type": Dict.tipo_consulta,
|
||||||
|
|
||||||
|
"patient_notes": "Prefiro horário pela manhã",
|
||||||
|
"insurance_provider": Dict.convenio,
|
||||||
|
"status": Dict.status,
|
||||||
|
"created_by": idUsuario
|
||||||
|
});
|
||||||
|
|
||||||
|
var requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: myHeaders,
|
||||||
|
body: raw,
|
||||||
|
redirect: 'follow'
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOptions)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(result => console.log(result))
|
||||||
|
.catch(error => console.log('error', error));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormConsultaPaciente agendamento={Dict} setAgendamento={setDict} onSave={handleSave} onCancel={() => navigate("/paciente/agendamento/")}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConsultaCadastroManager
|
||||||
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
|
||||||
155
src/PagesPaciente/ConsultasPaciente.jsx
Normal file
155
src/PagesPaciente/ConsultasPaciente.jsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import "./style.css"
|
||||||
|
import CardConsultaPaciente from './CardConsultaPaciente'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import API_KEY from '../components/utils/apiKeys'
|
||||||
|
import { useAuth } from '../components/utils/AuthProvider'
|
||||||
|
|
||||||
|
const ConsultasPaciente = ({setConsulta}) => {
|
||||||
|
const {getAuthorizationHeader} = useAuth()
|
||||||
|
|
||||||
|
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||||
|
const [selectedID, setSelectedId] = useState("")
|
||||||
|
let authHeader = getAuthorizationHeader()
|
||||||
|
|
||||||
|
const [consultas, setConsultas] = useState([])
|
||||||
|
|
||||||
|
const FiltrarAgendamentos = (agendamentos, id) => {
|
||||||
|
// Verifica se a lista de agendamentos é válida antes de tentar filtrar
|
||||||
|
if (!agendamentos || !Array.isArray(agendamentos)) {
|
||||||
|
console.error("A lista de agendamentos é inválida.");
|
||||||
|
setConsultas([]); // Garante que setConsultas receba uma lista vazia
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Filtragem
|
||||||
|
// O método .filter() cria uma nova lista contendo apenas os itens que retornarem 'true'
|
||||||
|
const consultasFiltradas = agendamentos.filter(agendamento => {
|
||||||
|
// A condição: o patient_id do agendamento deve ser estritamente igual ao id fornecido
|
||||||
|
// Usamos toString() para garantir a comparação, pois um pode ser number e o outro string
|
||||||
|
return agendamento.patient_id && agendamento.patient_id.toString() === id.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Adicionar a lista no setConsultas
|
||||||
|
console.log(consultasFiltradas)
|
||||||
|
setConsultas(consultasFiltradas);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exemplo de como você chamaria (assumindo que DadosAgendamento é sua lista original):
|
||||||
|
// FiltrarAgendamentos(DadosAgendamento, Paciente.id);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
var myHeaders = new Headers();
|
||||||
|
myHeaders.append("Authorization", authHeader);
|
||||||
|
myHeaders.append("apikey", API_KEY)
|
||||||
|
|
||||||
|
var requestOptions = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: myHeaders,
|
||||||
|
redirect: 'follow'
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(result => {FiltrarAgendamentos(result, "6e7f8829-0574-42df-9290-8dbb70f75ada" )})
|
||||||
|
.catch(error => console.log('error', error));
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
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>
|
||||||
|
<h1> Gerencie suas consultas</h1>
|
||||||
|
|
||||||
|
<div className='form-container'>
|
||||||
|
|
||||||
|
<button className="btn btn-primary" onClick={() => {navigate("criar")}}>
|
||||||
|
<i className="bi bi-plus-circle"></i> Adicionar Consulta
|
||||||
|
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h2>Seus proximos atendimentos</h2>
|
||||||
|
|
||||||
|
{consultas.map((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>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConsultasPaciente
|
||||||
320
src/PagesPaciente/FormConsultaPaciente.jsx
Normal file
320
src/PagesPaciente/FormConsultaPaciente.jsx
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
import InputMask from "react-input-mask";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
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?.dataAtendimento, 'aqui2')
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [selectedFile, setSelectedFile] = useState(null);
|
||||||
|
const [anexos, setAnexos] = useState([]);
|
||||||
|
const [loadingAnexos, setLoadingAnexos] = useState(false);
|
||||||
|
const [acessibilidade, setAcessibilidade] = useState({cadeirante:false,idoso:false,gravida:false,bebe:false, autista:false })
|
||||||
|
|
||||||
|
|
||||||
|
const [todosProfissionais, setTodosProfissionais] = useState([])
|
||||||
|
const [profissionaisFiltrados, setProfissionaisFiltrados] = useState([]);
|
||||||
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
const [horarioInicio, setHorarioInicio] = useState('');
|
||||||
|
const [horarioTermino, setHorarioTermino] = useState('');
|
||||||
|
|
||||||
|
const [horariosDisponiveis, sethorariosDisponiveis] = useState([])
|
||||||
|
|
||||||
|
let authHeader = getAuthorizationHeader()
|
||||||
|
|
||||||
|
const FormatCPF = (valor) => {
|
||||||
|
const digits = String(valor).replace(/\D/g, '').slice(0, 11);
|
||||||
|
return digits
|
||||||
|
.replace(/(\d{3})(\d)/, '$1.$2')
|
||||||
|
.replace(/(\d{3})(\d)/, '$1.$2')
|
||||||
|
.replace(/(\d{3})(\d{1,2})$/, '$1-$2');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const {value, name} = e.target;
|
||||||
|
console.log(value, name, agendamento)
|
||||||
|
|
||||||
|
if(name === 'email'){
|
||||||
|
setAgendamento({...agendamento, contato:{
|
||||||
|
...agendamento.contato,
|
||||||
|
email:value
|
||||||
|
}})}
|
||||||
|
else if(name === 'status'){
|
||||||
|
if(agendamento.status==='requested'){
|
||||||
|
setAgendamento((prev) => ({
|
||||||
|
...prev,
|
||||||
|
status:'confirmed',
|
||||||
|
}));
|
||||||
|
}else if(agendamento.status === 'confirmed'){
|
||||||
|
console.log(value)
|
||||||
|
setAgendamento((prev) => ({
|
||||||
|
...prev,
|
||||||
|
status:'requested',
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
|
||||||
|
else if(name === 'paciente_cpf'){
|
||||||
|
|
||||||
|
let cpfFormatted = FormatCPF(value)
|
||||||
|
const fetchPatient = async () => {
|
||||||
|
let patientData = await GetPatientByCPF(cpfFormatted, authHeader);
|
||||||
|
if (patientData) {
|
||||||
|
setAgendamento((prev) => ({
|
||||||
|
...prev,
|
||||||
|
paciente_nome: patientData.full_name,
|
||||||
|
patient_id: patientData.id
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
setAgendamento(prev => ({ ...prev, cpf: cpfFormatted }))
|
||||||
|
fetchPatient()
|
||||||
|
}else if(name==='convenio'){
|
||||||
|
setAgendamento({...agendamento,insurance_provider:value})
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
setAgendamento({...agendamento,[name]:value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ChamarMedicos = async () => {
|
||||||
|
const Medicos = await GetAllDoctors(authHeader)
|
||||||
|
setTodosProfissionais(Medicos)
|
||||||
|
}
|
||||||
|
ChamarMedicos();
|
||||||
|
|
||||||
|
var myHeaders = new Headers();
|
||||||
|
myHeaders.append("Content-Type", "application/json");
|
||||||
|
myHeaders.append("apikey", API_KEY)
|
||||||
|
myHeaders.append("Authorization", `Bearer ${authHeader.split(' ')[1]}`);
|
||||||
|
|
||||||
|
var raw = JSON.stringify({
|
||||||
|
"doctor_id": agendamento.doctor_id,
|
||||||
|
"start_date": agendamento.dataAtendimento,
|
||||||
|
"end_date": `${agendamento.dataAtendimento}T23:59:59.999Z`,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: myHeaders,
|
||||||
|
body: raw,
|
||||||
|
redirect: 'follow'
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/get-available-slots", requestOptions)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(result => {console.log(result); sethorariosDisponiveis(result)})
|
||||||
|
.catch(error => console.log('error', error));
|
||||||
|
|
||||||
|
}, [agendamento.dataAtendimento, agendamento.doctor_id])
|
||||||
|
|
||||||
|
|
||||||
|
// FUNÇÃO DE BUSCA E FILTRAGEM
|
||||||
|
const handleSearchProfissional = (e) => {
|
||||||
|
const term = e.target.value;
|
||||||
|
handleChange(e);
|
||||||
|
// 2. Lógica de filtragem:
|
||||||
|
if (term.trim() === '') {
|
||||||
|
setProfissionaisFiltrados([]);
|
||||||
|
setIsDropdownOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Adapte o nome da propriedade (ex: 'nome', 'full_name')
|
||||||
|
const filtered = todosProfissionais.filter(p =>
|
||||||
|
p.full_name.toLowerCase().includes(term.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
setProfissionaisFiltrados(filtered);
|
||||||
|
setIsDropdownOpen(filtered.length > 0); // Abre se houver resultados
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// FUNÇÃO PARA SELECIONAR UM ITEM DO DROPDOWN
|
||||||
|
const handleSelectProfissional = async (profissional) => {
|
||||||
|
setAgendamento(prev => ({
|
||||||
|
...prev,
|
||||||
|
doctor_id: profissional.id,
|
||||||
|
medico_nome: profissional.full_name
|
||||||
|
}));
|
||||||
|
// 2. Fecha o dropdown
|
||||||
|
setProfissionaisFiltrados([]);
|
||||||
|
setIsDropdownOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const formatarHora = (datetimeString) => {
|
||||||
|
return datetimeString.substring(11, 16);
|
||||||
|
};
|
||||||
|
|
||||||
|
const opcoesDeHorario = horariosDisponiveis?.slots?.map(item => ({
|
||||||
|
|
||||||
|
value: formatarHora(item.datetime),
|
||||||
|
label: formatarHora(item.datetime),
|
||||||
|
disabled: !item.available
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
alert("Agendamento salvo!");
|
||||||
|
navigate("/paciente/agendamento")
|
||||||
|
onSave({...agendamento, horarioInicio:horarioInicio})
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="form-container">
|
||||||
|
|
||||||
|
|
||||||
|
<form className="form-agendamento" onSubmit={handleSubmit}>
|
||||||
|
1
|
||||||
|
|
||||||
|
<h2 className="section-title">Informações do atendimento</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="campo-informacoes-atendimento">
|
||||||
|
|
||||||
|
<div className="campo-de-input-container"> {/* NOVO CONTAINER PAI */}
|
||||||
|
<div className="campo-de-input">
|
||||||
|
<label>Nome do profissional *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="medico_nome" // Use o nome correto da propriedade no estado `agendamento`
|
||||||
|
onChange={handleSearchProfissional}
|
||||||
|
value={agendamento.medico_nome}
|
||||||
|
autoComplete="off" // Ajuda a evitar o autocomplete nativo do navegador
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* DROPDOWN - RENDERIZAÇÃO CONDICIONAL */}
|
||||||
|
{isDropdownOpen && profissionaisFiltrados.length > 0 && (
|
||||||
|
<div className='dropdown-profissionais'>
|
||||||
|
{profissionaisFiltrados.map((profissional) => (
|
||||||
|
<div
|
||||||
|
key={profissional.id} // Use o ID do profissional
|
||||||
|
className='dropdown-item'
|
||||||
|
onClick={() => handleSelectProfissional(profissional)}
|
||||||
|
>
|
||||||
|
{profissional.full_name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tipo_atendimento">
|
||||||
|
<label>Tipo de atendimento *</label>
|
||||||
|
<select onChange={handleChange} name="tipo_atendimento" >
|
||||||
|
<option value="presencial" selected>Presencial</option>
|
||||||
|
<option value="teleconsulta">Teleconsulta</option>
|
||||||
|
</select>
|
||||||
|
</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>Data *</label>
|
||||||
|
<input type="date" name="dataAtendimento" value={agendamento.dataAtendimento} onChange={handleChange} required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="row">
|
||||||
|
<div className="campo-de-input">
|
||||||
|
<label htmlFor="inicio">Início *</label>
|
||||||
|
<select
|
||||||
|
id="inicio"
|
||||||
|
name="inicio"
|
||||||
|
required
|
||||||
|
value={horarioInicio}
|
||||||
|
onChange={(e) => setHorarioInicio(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="" disabled>Selecione a hora de início</option>
|
||||||
|
{opcoesDeHorario?.map((opcao, index) => (
|
||||||
|
<option
|
||||||
|
key={index}
|
||||||
|
value={opcao.value}
|
||||||
|
disabled={opcao.disabled}
|
||||||
|
>
|
||||||
|
{opcao.label}
|
||||||
|
{opcao.disabled && " (Indisponível)"}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* Dropdown de Término */}
|
||||||
|
<div className="campo-de-input">
|
||||||
|
<label htmlFor="termino">Término *</label>
|
||||||
|
<select
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="informacoes-atendimento-segunda-linha-direita">
|
||||||
|
|
||||||
|
|
||||||
|
<div className="campo-de-input">
|
||||||
|
<label>Observações</label>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormConsultaPaciente;
|
||||||
106
src/PagesPaciente/style.css
Normal file
106
src/PagesPaciente/style.css
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
|
||||||
|
/* Estilo geral do card para agrupar e dar um formato */
|
||||||
|
.card-consulta {
|
||||||
|
background-color: #007bff; /* Um tom de azul padrão */
|
||||||
|
display: flex; /* Para colocar horário e info lado a lado */
|
||||||
|
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 */
|
||||||
|
margin: 20px;
|
||||||
|
font-family: Arial, sans-serif; /* Fonte legível */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. Estilo para o Horário (Fundo Azul e Texto Branco/Grande) */
|
||||||
|
.horario-container {
|
||||||
|
background-color: #007bff; /* Um tom de azul padrão */
|
||||||
|
color: white; /* Texto branco */
|
||||||
|
padding: 15px 20px; /* Espaçamento interno */
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* Centraliza verticalmente */
|
||||||
|
justify-content: center; /* Centraliza horizontalmente */
|
||||||
|
flex-shrink: 0; /* Impede que o container do horário encolha */
|
||||||
|
border-right: 1px solid #0056b3; /* Uma linha sutil de separação */
|
||||||
|
}
|
||||||
|
|
||||||
|
.horario {
|
||||||
|
font-size: 2.2em; /* Torna o horário grande */
|
||||||
|
font-weight: bold; /* Deixa em negrito */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Estilo para as Informações (Fundo Branco) */
|
||||||
|
.info-container {
|
||||||
|
border-radius: 20px 0px 0px 20px;
|
||||||
|
background-color: white; /* Fundo branco, como solicitado */
|
||||||
|
color: #333; /* Cor escura para o texto para bom contraste */
|
||||||
|
padding: 15px; /* Espaçamento interno */
|
||||||
|
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 */
|
||||||
|
}
|
||||||
@ -3,8 +3,8 @@ import { GetPatientByID } from '../utils/Functions-Endpoints/Patient';
|
|||||||
import { useAuth } from '../utils/AuthProvider';
|
import { useAuth } from '../utils/AuthProvider';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import "./style/card-consulta.css"
|
||||||
const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal} ) => {
|
const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, setDictInfo, setSelectedId} ) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const {getAuthorizationHeader} = useAuth()
|
||||||
@ -45,9 +45,10 @@ const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal} )
|
|||||||
|
|
||||||
let nameArrayMedico = Medico?.full_name.split(' ')
|
let nameArrayMedico = Medico?.full_name.split(' ')
|
||||||
|
|
||||||
|
console.log(DadosConsulta.status)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`container-cardconsulta-${TabelaAgendamento}`}>
|
<div className={`container-cardconsulta container-cardconsulta-${TabelaAgendamento}`}>
|
||||||
|
|
||||||
{DadosConsulta.id?
|
{DadosConsulta.id?
|
||||||
|
|
||||||
@ -64,25 +65,25 @@ const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal} )
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='container-botons'>
|
<div className='actions-container'>
|
||||||
<button className="btn btn-sm btn-edit-custom"
|
<button className="btn btn-sm btn-edit-custom"
|
||||||
|
|
||||||
onClick={() => {navigate(`${DadosConsulta.id}/edit`)}}
|
onClick={() => {navigate(`2/edit`)
|
||||||
|
setDictInfo({...DadosConsulta,paciente_cpf:Paciente.cpf, paciente_nome:Paciente.full_name, nome_medico:Medico.full_name})
|
||||||
|
}}
|
||||||
|
|
||||||
>
|
>
|
||||||
<i className="bi bi-pencil me-1"></i> Editar
|
<i className="bi bi-pencil me-1"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-delete-custom"
|
className="btn btn-sm btn-delete-custom-style "
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log(DadosConsulta.id)
|
console.log(DadosConsulta.id)
|
||||||
//setSelectedPatientId(DadosConsulta.id);
|
setSelectedId(DadosConsulta.id);
|
||||||
setShowDeleteModal(true);
|
setShowDeleteModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i className="bi bi-trash me-1"></i> Excluir
|
<i className="bi bi-trash me-1"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -2,32 +2,36 @@ import InputMask from "react-input-mask";
|
|||||||
import "./style/formagendamentos.css";
|
import "./style/formagendamentos.css";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { GetPatientByCPF } from "../utils/Functions-Endpoints/Patient";
|
import { GetPatientByCPF } from "../utils/Functions-Endpoints/Patient";
|
||||||
import { GetDoctorByName } from "../utils/Functions-Endpoints/Doctor";
|
import { GetDoctorByName, GetAllDoctors } from "../utils/Functions-Endpoints/Doctor";
|
||||||
import { useAuth } from "../utils/AuthProvider";
|
import { useAuth } from "../utils/AuthProvider";
|
||||||
|
import API_KEY from "../utils/apiKeys";
|
||||||
|
|
||||||
const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) => {
|
const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) => {
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const {getAuthorizationHeader} = useAuth()
|
||||||
|
|
||||||
|
console.log(agendamento, 'aqui2')
|
||||||
|
|
||||||
|
const [sessoes,setSessoes] = useState(1)
|
||||||
|
const [tempoBaseConsulta, setTempoBaseConsulta] = useState(30); // NOVO: Tempo base da consulta em minutos
|
||||||
|
|
||||||
const [selectedFile, setSelectedFile] = useState(null);
|
const [selectedFile, setSelectedFile] = useState(null);
|
||||||
const [anexos, setAnexos] = useState([]);
|
const [anexos, setAnexos] = useState([]);
|
||||||
const [loadingAnexos, setLoadingAnexos] = useState(false);
|
const [loadingAnexos, setLoadingAnexos] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const [acessibilidade, setAcessibilidade] = useState({cadeirante:false,idoso:false,gravida:false,bebe:false, autista:false })
|
const [acessibilidade, setAcessibilidade] = useState({cadeirante:false,idoso:false,gravida:false,bebe:false, autista:false })
|
||||||
|
|
||||||
|
|
||||||
|
const [todosProfissionais, setTodosProfissionais] = useState([])
|
||||||
|
const [profissionaisFiltrados, setProfissionaisFiltrados] = useState([]);
|
||||||
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
const [horarioInicio, setHorarioInicio] = useState('');
|
||||||
|
const [horarioTermino, setHorarioTermino] = useState('');
|
||||||
|
|
||||||
|
const [horariosDisponiveis, sethorariosDisponiveis] = useState([])
|
||||||
|
|
||||||
let authHeader = getAuthorizationHeader()
|
let authHeader = getAuthorizationHeader()
|
||||||
|
|
||||||
|
|
||||||
const handleclickAcessibilidade = (id) => {
|
|
||||||
let resultado = acessibilidade[id]
|
|
||||||
|
|
||||||
if(resultado === false){ setAcessibilidade({...acessibilidade, [id]:true}); console.log('mudou')}
|
|
||||||
|
|
||||||
else if(resultado === true){ setAcessibilidade({...acessibilidade, [id]:false})}
|
|
||||||
console.log(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const FormatCPF = (valor) => {
|
const FormatCPF = (valor) => {
|
||||||
const digits = String(valor).replace(/\D/g, '').slice(0, 11);
|
const digits = String(valor).replace(/\D/g, '').slice(0, 11);
|
||||||
return digits
|
return digits
|
||||||
@ -39,15 +43,29 @@ const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) =>
|
|||||||
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
|
|
||||||
const {value, name} = e.target;
|
const {value, name} = e.target;
|
||||||
|
console.log(value, name, agendamento)
|
||||||
|
|
||||||
if(name === 'email'){
|
if(name === 'email'){
|
||||||
setAgendamento({...agendamento, contato:{
|
setAgendamento({...agendamento, contato:{
|
||||||
...agendamento.contato,
|
...agendamento.contato,
|
||||||
email:value
|
email:value
|
||||||
}})
|
}})}
|
||||||
|
else if(name === 'status'){
|
||||||
|
if(agendamento.status==='requested'){
|
||||||
|
setAgendamento((prev) => ({
|
||||||
|
...prev,
|
||||||
|
status:'confirmed',
|
||||||
|
}));
|
||||||
|
}else if(agendamento.status === 'confirmed'){
|
||||||
|
console.log(value)
|
||||||
|
setAgendamento((prev) => ({
|
||||||
|
...prev,
|
||||||
|
status:'requested',
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
|
||||||
}else if(name === 'cpf'){
|
else if(name === 'paciente_cpf'){
|
||||||
|
|
||||||
let cpfFormatted = FormatCPF(value)
|
let cpfFormatted = FormatCPF(value)
|
||||||
const fetchPatient = async () => {
|
const fetchPatient = async () => {
|
||||||
@ -55,7 +73,7 @@ const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) =>
|
|||||||
if (patientData) {
|
if (patientData) {
|
||||||
setAgendamento((prev) => ({
|
setAgendamento((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
nome: patientData.full_name,
|
paciente_nome: patientData.full_name,
|
||||||
patient_id: patientData.id
|
patient_id: patientData.id
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
@ -63,28 +81,124 @@ const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) =>
|
|||||||
fetchPatient()
|
fetchPatient()
|
||||||
}else if(name==='convenio'){
|
}else if(name==='convenio'){
|
||||||
setAgendamento({...agendamento,insurance_provider:value})
|
setAgendamento({...agendamento,insurance_provider:value})
|
||||||
}else if(name ==='profissional'){
|
|
||||||
|
|
||||||
|
|
||||||
const fetchDoctor = async () => {
|
|
||||||
let DoctorData = await GetDoctorByName(value, authHeader)
|
|
||||||
if(DoctorData){
|
|
||||||
setAgendamento((prev) => ({
|
|
||||||
...prev,
|
|
||||||
doctor_id:DoctorData.id
|
|
||||||
}))
|
|
||||||
}}
|
|
||||||
fetchDoctor()
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
setAgendamento({...agendamento,[name]:value})
|
setAgendamento({...agendamento,[name]:value})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ChamarMedicos = async () => {
|
||||||
|
const Medicos = await GetAllDoctors(authHeader)
|
||||||
|
setTodosProfissionais(Medicos)
|
||||||
|
}
|
||||||
|
ChamarMedicos();
|
||||||
|
|
||||||
|
var myHeaders = new Headers();
|
||||||
|
myHeaders.append("Content-Type", "application/json");
|
||||||
|
myHeaders.append("apikey", API_KEY)
|
||||||
|
myHeaders.append("Authorization", `Bearer ${authHeader.split(' ')[1]}`);
|
||||||
|
|
||||||
|
var raw = JSON.stringify({
|
||||||
|
"doctor_id": agendamento.doctor_id,
|
||||||
|
"start_date": agendamento.dataAtendimento,
|
||||||
|
"end_date": `${agendamento.dataAtendimento}T23:59:59.999Z`,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: myHeaders,
|
||||||
|
body: raw,
|
||||||
|
redirect: 'follow'
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/get-available-slots", requestOptions)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(result => {console.log(result); sethorariosDisponiveis(result)})
|
||||||
|
.catch(error => console.log('error', error));
|
||||||
|
|
||||||
|
}, [agendamento.dataAtendimento, agendamento.doctor_id])
|
||||||
|
|
||||||
|
|
||||||
|
// FUNÇÃO DE BUSCA E FILTRAGEM
|
||||||
|
const handleSearchProfissional = (e) => {
|
||||||
|
const term = e.target.value;
|
||||||
|
handleChange(e);
|
||||||
|
// 2. Lógica de filtragem:
|
||||||
|
if (term.trim() === '') {
|
||||||
|
setProfissionaisFiltrados([]);
|
||||||
|
setIsDropdownOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Adapte o nome da propriedade (ex: 'nome', 'full_name')
|
||||||
|
const filtered = todosProfissionais.filter(p =>
|
||||||
|
p.full_name.toLowerCase().includes(term.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
setProfissionaisFiltrados(filtered);
|
||||||
|
setIsDropdownOpen(filtered.length > 0); // Abre se houver resultados
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// FUNÇÃO PARA SELECIONAR UM ITEM DO DROPDOWN
|
||||||
|
const handleSelectProfissional = async (profissional) => {
|
||||||
|
setAgendamento(prev => ({
|
||||||
|
...prev,
|
||||||
|
doctor_id: profissional.id,
|
||||||
|
nome_medico: profissional.full_name
|
||||||
|
}));
|
||||||
|
// 2. Fecha o dropdown
|
||||||
|
setProfissionaisFiltrados([]);
|
||||||
|
setIsDropdownOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const formatarHora = (datetimeString) => {
|
||||||
|
return datetimeString.substring(11, 16);
|
||||||
|
};
|
||||||
|
|
||||||
|
const opcoesDeHorario = horariosDisponiveis?.slots?.map(item => ({
|
||||||
|
|
||||||
|
value: formatarHora(item.datetime),
|
||||||
|
label: formatarHora(item.datetime),
|
||||||
|
disabled: !item.available
|
||||||
|
}));
|
||||||
|
|
||||||
|
const calcularHorarioTermino = (inicio, sessoes, tempoBase) => {
|
||||||
|
if (!inicio || inicio.length !== 5 || !inicio.includes(':')) return '';
|
||||||
|
|
||||||
|
const [horas, minutos] = inicio.split(':').map(Number);
|
||||||
|
const minutosInicio = (horas * 60) + minutos;
|
||||||
|
const duracaoTotalMinutos = sessoes * tempoBase;
|
||||||
|
const minutosTermino = minutosInicio + duracaoTotalMinutos;
|
||||||
|
|
||||||
|
const horaTermino = Math.floor(minutosTermino / 60) % 24;
|
||||||
|
const minutoTermino = minutosTermino % 60;
|
||||||
|
|
||||||
|
const formatar = (num) => String(num).padStart(2, '0');
|
||||||
|
|
||||||
|
return `${formatar(horaTermino)}:${formatar(minutoTermino)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Recalcula o horário de término sempre que houver alteração nas variáveis dependentes
|
||||||
|
const novoTermino = calcularHorarioTermino(horarioInicio, sessoes, tempoBaseConsulta);
|
||||||
|
setHorarioTermino(novoTermino);
|
||||||
|
|
||||||
|
setAgendamento(prev => ({
|
||||||
|
...prev,
|
||||||
|
horarioTermino: novoTermino
|
||||||
|
}));
|
||||||
|
}, [horarioInicio, sessoes, tempoBaseConsulta, setAgendamento]);
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert("Agendamento salvo!");
|
alert("Agendamento salvo!");
|
||||||
onSave(agendamento)
|
onSave({...agendamento, horarioInicio:horarioInicio})
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -95,26 +209,21 @@ const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) =>
|
|||||||
<h2 className="section-title">Informações do paciente</h2>
|
<h2 className="section-title">Informações do paciente</h2>
|
||||||
|
|
||||||
<div className="campos-informacoes-paciente" id="informacoes-paciente-linha-um">
|
<div className="campos-informacoes-paciente" id="informacoes-paciente-linha-um">
|
||||||
<div className="campo-de-input">
|
|
||||||
<label>Nome *</label>
|
|
||||||
<input type="text" name="nome" value={agendamento.nome} placeholder="Insira o nome do paciente" required onChange={handleChange} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
<label>CPF do paciente</label>
|
<label>CPF do paciente</label>
|
||||||
|
<input type="text" name="paciente_cpf" placeholder="000.000.000-00" onChange={handleChange} value={agendamento.paciente_cpf}/>
|
||||||
<input type="text" name="cpf" placeholder="000.000.000-00" onChange={handleChange} value={agendamento.cpf}/>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="campos-informacoes-paciente" id="informacoes-paciente-linha-tres">
|
|
||||||
|
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
|
<label>Nome *</label>
|
||||||
|
<input type="text" name="paciente_nome" value={agendamento.paciente_nome} placeholder="Insira o nome do paciente" required onChange={handleChange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="campos-informacoes-paciente" id="informacoes-paciente-linha-tres">
|
||||||
|
|
||||||
|
<div >
|
||||||
<label>Convênio</label>
|
<label>Convênio</label>
|
||||||
<select name="convenio" onChange={handleChange}>
|
<select name="convenio" onChange={handleChange}>
|
||||||
<option value="publico">Público</option>
|
<option value="publico">Público</option>
|
||||||
@ -126,115 +235,125 @@ const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) =>
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="section-subtitle">Informações adicionais</h3>
|
|
||||||
|
|
||||||
<label htmlFor="anexo-input" className="btn btn-secondary">Adicionar Anexo</label>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
id="anexo-input"
|
|
||||||
className="d-none"
|
|
||||||
onChange={(e) => setSelectedFile(e.target.files[0])}
|
|
||||||
/>
|
|
||||||
{selectedFile && (
|
|
||||||
<button type="button" className="btn btn-primary ms-2" >
|
|
||||||
Enviar
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<div className="anexos-list">
|
|
||||||
{loadingAnexos ? (
|
|
||||||
<p>Carregando anexos...</p>
|
|
||||||
) : (
|
|
||||||
anexos.map((anexo, index) => (
|
|
||||||
<div key={index} className="anexo-item">
|
|
||||||
<span>{anexo.nome || anexo.fileName}</span>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<h2 className="section-title">Informações do atendimento</h2>
|
<h2 className="section-title">Informações do atendimento</h2>
|
||||||
|
|
||||||
|
|
||||||
<div className="icons-container">
|
|
||||||
|
|
||||||
<div className={`icons-div ${ acessibilidade.cadeirante === true ? 'acessibilidade-ativado' : ''} `} id='cadeirante' onClick={(e) => handleclickAcessibilidade(e.currentTarget.id)}>
|
|
||||||
|
|
||||||
<span className="material-symbols-outlined icon">accessible</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={`icons-div ${acessibilidade.idoso === true ? 'acessibilidade-ativado' : ''}`} id="idoso" onClick={(e) => handleclickAcessibilidade(e.currentTarget.id)}>
|
|
||||||
<span className="material-symbols-outlined icon">elderly</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={`icons-div ${acessibilidade.gravida === true ? 'acessibilidade-ativado' : ''}`} id="gravida" onClick={(e) => handleclickAcessibilidade(e.currentTarget.id)}>
|
|
||||||
<span className="material-symbols-outlined icon">pregnant_woman</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={`icons-div ${acessibilidade.bebe === true ? 'acessibilidade-ativado' : ''}`} id="bebe" onClick={(e) => handleclickAcessibilidade(e.currentTarget.id)}>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-baby-icon lucide-baby"><path d="M10 16c.5.3 1.2.5 2 .5s1.5-.2 2-.5"/><path d="M15 12h.01"/><path d="M19.38 6.813A9 9 0 0 1 20.8 10.2a2 2 0 0 1 0 3.6 9 9 0 0 1-17.6 0 2 2 0 0 1 0-3.6A9 9 0 0 1 12 3c2 0 3.5 1.1 3.5 2.5s-.9 2.5-2 2.5c-.8 0-1.5-.4-1.5-1"/><path d="M9 12h.01"/></svg>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={`icons-div ${acessibilidade.autista === true ? 'acessibilidade-ativado' : ''}`} id="autista" onClick={(e) => handleclickAcessibilidade(e.currentTarget.id)}>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.75" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-puzzle-icon lucide-puzzle"><path d="M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z"/></svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="campo-informacoes-atendimento">
|
<div className="campo-informacoes-atendimento">
|
||||||
|
|
||||||
|
<div className="campo-de-input-container"> {/* NOVO CONTAINER PAI */}
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
<label>Nome do profissional *</label>
|
<label>Nome do profissional *</label>
|
||||||
<input type="text" name="profissional" onChange={handleChange} value={agendamento.nome_medico}required />
|
<input
|
||||||
|
type="text"
|
||||||
|
name="nome_medico" // Use o nome correto da propriedade no estado `agendamento`
|
||||||
|
onChange={handleSearchProfissional}
|
||||||
|
value={agendamento?.nome_medico}
|
||||||
|
autoComplete="off" // Ajuda a evitar o autocomplete nativo do navegador
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* DROPDOWN - RENDERIZAÇÃO CONDICIONAL */}
|
||||||
|
{isDropdownOpen && profissionaisFiltrados.length > 0 && (
|
||||||
|
<div className='dropdown-profissionais'>
|
||||||
|
{profissionaisFiltrados.map((profissional) => (
|
||||||
|
<div
|
||||||
|
key={profissional.id} // Use o ID do profissional
|
||||||
|
className='dropdown-item'
|
||||||
|
onClick={() => handleSelectProfissional(profissional)}
|
||||||
|
>
|
||||||
|
{profissional.full_name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="campo-de-input">
|
<div className="tipo_atendimento">
|
||||||
<label>Tipo de atendimento *</label>
|
<label>Tipo de atendimento *</label>
|
||||||
<input type="text" name="tipoAtendimento" required />
|
<select onChange={handleChange} name="tipo_atendimento" >
|
||||||
|
<option value="presencial" selected>Presencial</option>
|
||||||
|
<option value="teleconsulta">Teleconsulta</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section id="informacoes-atendimento-segunda-linha">
|
<section id="informacoes-atendimento-segunda-linha">
|
||||||
<section id="informacoes-atendimento-segunda-linha-esquerda">
|
<section id="informacoes-atendimento-segunda-linha-esquerda">
|
||||||
|
|
||||||
<div className="campo-informacoes-atendimento">
|
<div className="campo-informacoes-atendimento">
|
||||||
<div className='campo-de-input'>
|
|
||||||
<label>Unidade *</label>
|
|
||||||
<select name="unidade">
|
|
||||||
<option value="" disabled invisible selected>Selecione a unidade</option>
|
|
||||||
<option value="centro">Núcleo de Especialidades Integradas</option>
|
|
||||||
<option value="leste">Unidade Leste</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
<label>Data *</label>
|
<label>Data *</label>
|
||||||
<input type="date" name="dataAtendimento" required />
|
<input type="date" name="dataAtendimento" onChange={handleChange} required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="linha">
|
||||||
<div className="campo-informacoes-atendimento">
|
{/* Dropdown de Início (Não modificado) */}
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
<label>Início *</label>
|
<label htmlFor="inicio">Início *</label>
|
||||||
<input type="time" name="inicio" required />
|
<select
|
||||||
</div>
|
id="inicio"
|
||||||
|
name="inicio"
|
||||||
<div className="campo-de-input">
|
required
|
||||||
<label>Término *</label>
|
value={horarioInicio}
|
||||||
<input type="time" name="termino" required />
|
onChange={(e) => setHorarioInicio(e.target.value)}
|
||||||
</div>
|
>
|
||||||
|
<option value="" disabled>Selecione a hora de início</option>
|
||||||
<div className="campo-de-input">
|
{opcoesDeHorario?.map((opcao, index) => (
|
||||||
<label>Profissional solicitante</label>
|
<option
|
||||||
<select name="solicitante">
|
key={index}
|
||||||
<option value="" disabled invisible selected>Selecione o solicitante</option>
|
value={opcao.value}
|
||||||
<option value="secretaria">Secretária</option>
|
disabled={opcao.disabled}
|
||||||
<option value="medico">Médico</option>
|
>
|
||||||
|
{opcao.label}
|
||||||
|
{opcao.disabled && " (Indisponível)"}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</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>
|
||||||
|
</div>
|
||||||
|
<div className="campo-de-input">
|
||||||
|
<label htmlFor="termino">Término *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="termino"
|
||||||
|
name="termino"
|
||||||
|
value={horarioTermino || '— —'}
|
||||||
|
readOnly
|
||||||
|
className="horario-termino-readonly"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="informacoes-atendimento-segunda-linha-direita">
|
<section className="informacoes-atendimento-segunda-linha-direita">
|
||||||
@ -245,13 +364,21 @@ const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) =>
|
|||||||
<textarea name="observacoes" rows="4" cols="1"></textarea>
|
<textarea name="observacoes" rows="4" cols="1"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<div className="form-actions">
|
<div className="form-actions">
|
||||||
<button type="submit" className="btn-primary">Salvar agendamento</button>
|
<button type="submit" className="btn-primary">Salvar agendamento</button>
|
||||||
<button type="button" className="btn-cancel" onClick={onCancel}>Cancelar</button>
|
<button type="button" className="btn-cancel" onClick={onCancel}>Cancelar</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div className="campo-de-input-check">
|
||||||
|
<input className="form-check-input form-custom-check" type="checkbox" name="status" onChange={handleChange} />
|
||||||
|
<label className="form-check-label checkbox-label" htmlFor="status">
|
||||||
|
Adicionar a fila de espera
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import CardConsulta from './CardConsulta';
|
import CardConsulta from './CardConsulta';
|
||||||
import "./style/styleTabelas/tabeladia.css";
|
import "./style/styleTabelas/tabeladia.css";
|
||||||
|
|
||||||
const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDeleteModal }) => {
|
const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDeleteModal, setDictInfo, setSelectedId }) => {
|
||||||
const [indiceAcesso, setIndiceAcesso] = useState(0)
|
const [indiceAcesso, setIndiceAcesso] = useState(0)
|
||||||
const [Dia, setDia] = useState()
|
const [Dia, setDia] = useState()
|
||||||
const agendamentosDoDia = agendamentos?.semana1?.segunda || [];
|
const agendamentosDoDia = agendamentos?.semana1?.segunda || [];
|
||||||
@ -10,6 +10,7 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
|
|||||||
|
|
||||||
let ListaDiasComAgendamentos = Object.keys(agendamentos)
|
let ListaDiasComAgendamentos = Object.keys(agendamentos)
|
||||||
|
|
||||||
|
console.log(agendamentos)
|
||||||
|
|
||||||
|
|
||||||
//console.log(Dia, "hshdhshhsdhs")
|
//console.log(Dia, "hshdhshhsdhs")
|
||||||
@ -24,29 +25,37 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
|
|||||||
<div>
|
<div>
|
||||||
<div id='tabela-seletor-container'>
|
<div id='tabela-seletor-container'>
|
||||||
<button onClick={() => {if(indiceAcesso === 0)return; else(setIndiceAcesso(indiceAcesso - 1))}}> <i className="bi bi-chevron-compact-left"></i></button>
|
<button onClick={() => {if(indiceAcesso === 0)return; else(setIndiceAcesso(indiceAcesso - 1))}}> <i className="bi bi-chevron-compact-left"></i></button>
|
||||||
<p>{Dia}</p>
|
|
||||||
|
|
||||||
|
<p>{Dia ? `${Dia?.split('-')[2]}/${Dia?.split('-')[1]}/${Dia?.split('-')[0]}`: ''}</p>
|
||||||
|
|
||||||
<button onClick={() => {if(ListaDiasComAgendamentos.length - 1 === indiceAcesso)return; else(setIndiceAcesso(indiceAcesso + 1))}}> <i className="bi bi-chevron-compact-right"></i></button>
|
<button onClick={() => {if(ListaDiasComAgendamentos.length - 1 === indiceAcesso)return; else(setIndiceAcesso(indiceAcesso + 1))}}> <i className="bi bi-chevron-compact-right"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table className='tabeladiaria'>
|
<table className='tabeladiaria'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Horário</th>
|
<th className='cabecalho-horario'>Horário</th>
|
||||||
<th>{}</th>
|
<th>{}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{agendamentos[Dia]?.map((agendamento, index) => (
|
{agendamentos[Dia]?.map((agendamento, index) => {
|
||||||
|
|
||||||
|
let Data =agendamento.scheduled_at.split("T")
|
||||||
|
let horario = Data[1].split(':')
|
||||||
|
return(
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td><p>{agendamento.horario}</p></td>
|
|
||||||
|
<td className='coluna-horario'><p className='horario-texto'>{`${horario[0]}:${horario[1]}`}</p></td>
|
||||||
<td className='mostrar-horario'>
|
<td className='mostrar-horario'>
|
||||||
<div onClick={() => handleClickAgendamento(agendamento)}>
|
<div onClick={() => handleClickAgendamento(agendamento)}>
|
||||||
<CardConsulta DadosConsulta={agendamento} TabelaAgendamento={'dia'} setShowDeleteModal={setShowDeleteModal} />
|
<CardConsulta DadosConsulta={agendamento} TabelaAgendamento={'dia'} setShowDeleteModal={setShowDeleteModal} setDictInfo={setDictInfo} setSelectedId={setSelectedId}/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
)})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import "./style/styleTabelas/tabelames.css";
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos }) => {
|
const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos, setShowDeleteModal, setSelectedId ,setDictInfo }) => {
|
||||||
|
|
||||||
const dataHoje = dayjs();
|
const dataHoje = dayjs();
|
||||||
const AnoAtual = dataHoje.year();
|
const AnoAtual = dataHoje.year();
|
||||||
@ -88,8 +88,6 @@ const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(OrganizarAgendamentosMensais)
|
console.log(OrganizarAgendamentosMensais)
|
||||||
|
|
||||||
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -185,11 +183,11 @@ const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos }) => {
|
|||||||
<table className='tabelamensal'>
|
<table className='tabelamensal'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='cabecalho-tabela'>Seg</td>
|
<th className='cabecalho-tabela'>Segunda</th>
|
||||||
<th>Ter</th>
|
<th>Terça</th>
|
||||||
<th>Qua</th>
|
<th>Quarta</th>
|
||||||
<th>Qui</th>
|
<th>Quinta</th>
|
||||||
<th>Sex</th>
|
<th>Sexta</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -204,9 +202,9 @@ const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos }) => {
|
|||||||
{
|
{
|
||||||
semana && typeof semana === "object" && Object.keys(semana).map((dia) => (
|
semana && typeof semana === "object" && Object.keys(semana).map((dia) => (
|
||||||
<td key={dia} >
|
<td key={dia} >
|
||||||
<CardConsulta DadosConsulta={((semana[dia]|| [])[0]) || {status:'vazio'}}/>
|
<CardConsulta DadosConsulta={((semana[dia]|| [])[0]) || {status:'vazio'}} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>
|
||||||
<CardConsulta DadosConsulta={((semana[dia]|| [])[1]) || {status:'vazio'}}/>
|
<CardConsulta DadosConsulta={((semana[dia]|| [])[1]) || {status:'vazio'}} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>
|
||||||
<CardConsulta DadosConsulta={((semana[dia]|| [])[2]) || {status:'vazio'}}/>
|
<CardConsulta DadosConsulta={((semana[dia]|| [])[2]) || {status:'vazio'}} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>
|
||||||
{semana[dia].length > 3 ? (
|
{semana[dia].length > 3 ? (
|
||||||
<div>
|
<div>
|
||||||
<p>{` +${semana[dia].length - 2}`}</p>
|
<p>{` +${semana[dia].length - 2}`}</p>
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { useEffect, useState, useMemo } from 'react';
|
|||||||
import weekOfYear from 'dayjs/plugin/weekOfYear'
|
import weekOfYear from 'dayjs/plugin/weekOfYear'
|
||||||
dayjs.extend(weekOfYear)
|
dayjs.extend(weekOfYear)
|
||||||
|
|
||||||
const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes }) => {
|
const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteModal ,setSelectedId ,setDictInfo}) => {
|
||||||
|
|
||||||
// Armazena o objeto COMPLETO das semanas organizadas
|
// Armazena o objeto COMPLETO das semanas organizadas
|
||||||
const [semanasOrganizadas, setSemanasOrganizadas] = useState({});
|
const [semanasOrganizadas, setSemanasOrganizadas] = useState({});
|
||||||
@ -75,7 +75,8 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSemanasOrganizadas(OrganizarAgendamentosSemanais);
|
setSemanasOrganizadas(OrganizarAgendamentosSemanais);
|
||||||
// NOTA: Ao carregar, o Indice é 0, que é a primeira semana.
|
|
||||||
|
//console.log(semanasOrganizadas, `aqui`)
|
||||||
}, [OrganizarAgendamentosSemanais])
|
}, [OrganizarAgendamentosSemanais])
|
||||||
|
|
||||||
// --- NOVAS FUNÇÕES DE NAVEGAÇÃO ---
|
// --- NOVAS FUNÇÕES DE NAVEGAÇÃO ---
|
||||||
@ -156,44 +157,56 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{indicesDeLinha.map((indiceLinha) => (
|
{indicesDeLinha.map((indiceLinha) => {
|
||||||
|
|
||||||
|
let schedulet_at = semanaParaRenderizar.segunda[indiceLinha].scheduled_at.split("T")
|
||||||
|
|
||||||
|
let horario = schedulet_at[1].split(":")
|
||||||
|
|
||||||
|
console.log(horario)
|
||||||
|
|
||||||
|
return(
|
||||||
<tr key={indiceLinha}>
|
<tr key={indiceLinha}>
|
||||||
{/* Célula para Horário (Pode ser ajustado para mostrar o horário real) */}
|
{/* Célula para Horário (Pode ser ajustado para mostrar o horário real) */}
|
||||||
<td></td>
|
<td>
|
||||||
|
|
||||||
|
<p className='horario-texto'> {`${horario[0]}:${horario[1]}`} </p>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
{/* Mapeamento de COLUNAS (dias) */}
|
{/* Mapeamento de COLUNAS (dias) */}
|
||||||
<td>
|
<td>
|
||||||
{semanaParaRenderizar.segunda[indiceLinha]
|
{semanaParaRenderizar.segunda[indiceLinha]
|
||||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.segunda[indiceLinha]} />
|
? <CardConsulta DadosConsulta={semanaParaRenderizar.segunda[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{semanaParaRenderizar.terça[indiceLinha]
|
{semanaParaRenderizar.terça[indiceLinha]
|
||||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.terça[indiceLinha]} />
|
? <CardConsulta DadosConsulta={semanaParaRenderizar.terça[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{semanaParaRenderizar.quarta[indiceLinha]
|
{semanaParaRenderizar.quarta[indiceLinha]
|
||||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.quarta[indiceLinha]} />
|
? <CardConsulta DadosConsulta={semanaParaRenderizar.quarta[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{semanaParaRenderizar.quinta[indiceLinha]
|
{semanaParaRenderizar.quinta[indiceLinha]
|
||||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.quinta[indiceLinha]} />
|
? <CardConsulta DadosConsulta={semanaParaRenderizar.quinta[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{semanaParaRenderizar.sexta[indiceLinha]
|
{semanaParaRenderizar.sexta[indiceLinha]
|
||||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.sexta[indiceLinha]} />
|
? <CardConsulta DadosConsulta={semanaParaRenderizar.sexta[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
)})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
61
src/components/AgendarConsulta/style/card-consulta.css
Normal file
61
src/components/AgendarConsulta/style/card-consulta.css
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
.actions-container {
|
||||||
|
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 */
|
||||||
|
.container-cardconsulta: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. Estilos para os ícones dentro dos botões (já está no JSX com fs-4) */
|
||||||
|
/* .fs-4 do Bootstrap já cuida do tamanho do ícone. Se precisar de mais controle, adicione aqui. */
|
||||||
|
.action-button .bi {
|
||||||
|
/* Exemplo: se precisar de um ajuste fino além do fs-4 */
|
||||||
|
/* font-size: 1.5rem; */
|
||||||
|
}
|
||||||
@ -110,6 +110,7 @@ svg{
|
|||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
@ -304,3 +305,170 @@ html[data-bs-theme="dark"] .icon,
|
|||||||
html[data-bs-theme="dark"] svg {
|
html[data-bs-theme="dark"] svg {
|
||||||
color: #e0e0e0 !important;
|
color: #e0e0e0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* CONTAINER PAI - ESSENCIAL PARA POSICIONAMENTO */
|
||||||
|
.campo-de-input-container {
|
||||||
|
position: relative; /* Define o contexto para o dropdown */
|
||||||
|
/* ... outros estilos de layout (display, margin, etc.) ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ESTILO DA LISTA DROPDOWN */
|
||||||
|
.dropdown-profissionais {
|
||||||
|
position: absolute; /* Flutua em relação ao pai (.campo-de-input-container) */
|
||||||
|
top: 100%; /* Começa logo abaixo do input */
|
||||||
|
left: 0;
|
||||||
|
width: 100%; /* Ocupa toda a largura do container pai */
|
||||||
|
|
||||||
|
/* Estilos visuais */
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 100; /* Alto z-index para garantir que fique acima de outros elementos */
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ESTILO DE CADA ITEM DO DROPDOWN */
|
||||||
|
.dropdown-item {
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tipo_atendimento{
|
||||||
|
margin-left: 3rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 1. Estilização Básica e Tamanho (Estado Padrão - Antes de Clicar) */
|
||||||
|
.checkbox-customs {
|
||||||
|
/* Remove a aparência padrão do navegador/Bootstrap */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
|
||||||
|
/* Define o tamanho desejado */
|
||||||
|
width: 1.2rem; /* Ajuste conforme o seu gosto (ex: 1.2rem = 19.2px) */
|
||||||
|
height: 1.2rem;
|
||||||
|
|
||||||
|
/* Define o visual "branco com borda preta" */
|
||||||
|
background-color: #fff; /* Fundo branco */
|
||||||
|
border: 1px solid #000; /* Borda preta de 1px */
|
||||||
|
border-radius: 0.25rem; /* Borda levemente arredondada (opcional, imita Bootstrap) */
|
||||||
|
|
||||||
|
/* Centraliza o 'check' (quando aparecer) */
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer; /* Indica que é clicável */
|
||||||
|
|
||||||
|
/* Adiciona a transição suave */
|
||||||
|
transition: all 0.5s ease; /* Transição em 0.5 segundos para todas as propriedades */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Estilização no Estado Clicado (:checked) */
|
||||||
|
.checkbox-customs:checked {
|
||||||
|
/* Quando clicado, mantém o fundo branco (se quiser mudar, altere aqui) */
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
/* Se você quiser que a borda mude de cor ao clicar, altere aqui. */
|
||||||
|
/* border-color: #007bff; */ /* Exemplo: borda azul */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Ocultar o 'Check' Padrão e Criar um Check Customizado */
|
||||||
|
/* O Bootstrap/Navegador insere um ícone de 'check'. Vamos controlá-lo com background-image. */
|
||||||
|
.checkbox-customs:checked {
|
||||||
|
/* Este código do Bootstrap usa um SVG para o ícone de 'check' */
|
||||||
|
/* Aqui, estamos forçando o ícone de 'check' a ser preto para combinar com a borda preta. */
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
|
||||||
|
|
||||||
|
/* Garante que o ícone fique centralizado e preencha o espaço */
|
||||||
|
background-size: 100% 100%;
|
||||||
|
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) */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,47 @@
|
|||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
border: 4px solid #4a90e2; /* borda azul, altere para a cor desejada */
|
border: 4px solid #4a90e2; /* borda azul, altere para a cor desejada */
|
||||||
}
|
}
|
||||||
|
/* 1. Estilização do TD (Container) */
|
||||||
|
.coluna-horario {
|
||||||
|
/* Garante que o TD tenha um preenchimento generoso para parecer maior */
|
||||||
|
padding: 20px 10px; /* Ajuste estes valores conforme a necessidade */
|
||||||
|
|
||||||
|
/* Centraliza o conteúdo (o <p>) vertical e horizontalmente */
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
/* Cor de fundo clara para contrastar levemente, se desejar */
|
||||||
|
/* background-color: #f9f9f9; */
|
||||||
|
|
||||||
|
/* Garante que a coluna não fique muito estreita */
|
||||||
|
width: 15%; /* Exemplo, ajuste se necessário */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Estilização do P (O texto do horário) */
|
||||||
|
.horario-texto {
|
||||||
|
/* Remove a margem padrão do <p> para alinhamento preciso */
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
/* Aplica a cor azul condizente com o "10:30" da imagem */
|
||||||
|
color: #007bff; /* Tonalidade de azul Bootstrap/padrão */
|
||||||
|
|
||||||
|
/* Aumenta a fonte para dar mais destaque */
|
||||||
|
font-size: 1.3em; /* 1.3 vezes o tamanho normal */
|
||||||
|
|
||||||
|
/* Deixa a fonte mais pesada para ser mais visível */
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
/* Se você quiser garantir que a fonte seja a mesma do resto do site */
|
||||||
|
/* font-family: 'Arial', sans-serif; */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Estilização do cabeçalho (opcional, para consistência) */
|
||||||
|
.cabecalho-horario {
|
||||||
|
background-color: #007bff; /* Cor de fundo azul como na imagem */
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Células da tabela */
|
/* Células da tabela */
|
||||||
.tabeladiaria th, .tabeladiaria td {
|
.tabeladiaria th, .tabeladiaria td {
|
||||||
@ -51,7 +92,6 @@ font-weight: 600;
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ajuste para a classe calendario, se for usada */
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
/* Cabeçalho */
|
/* Cabeçalho */
|
||||||
.tabelamensal thead th {
|
.tabelamensal thead th {
|
||||||
background-color: #0078d7;
|
background-color: #0078d7;
|
||||||
color: #0078d7;
|
color: white;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-bottom: 2px solid #0078d7; /* borda inferior mais forte no cabeçalho */
|
border-bottom: 2px solid #0078d7; /* borda inferior mais forte no cabeçalho */
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useState, useRef, useCallback } from "react";
|
||||||
import { Link, useNavigate, useLocation } from "react-router-dom";
|
import { Link, useNavigate, useLocation } from "react-router-dom";
|
||||||
import "./DoctorForm.css";
|
import "./DoctorForm.css";
|
||||||
import HorariosDisponibilidade from "../doctors/HorariosDisponibilidade";
|
import HorariosDisponibilidade from "../doctors/HorariosDisponibilidade";
|
||||||
|
|
||||||
|
const ENDPOINT_AVAILABILITY =
|
||||||
|
"https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability";
|
||||||
|
|
||||||
function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
|
|
||||||
const FormatTelefones = (valor) => {
|
const FormatTelefones = (valor) => {
|
||||||
const digits = String(valor).replace(/\D/g, "").slice(0, 11);
|
const digits = String(valor).replace(/\D/g, "").slice(0, 11);
|
||||||
return digits
|
return digits
|
||||||
@ -28,7 +32,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
const cpfLimpo = cpf.replace(/\D/g, "");
|
const cpfLimpo = cpf.replace(/\D/g, "");
|
||||||
|
|
||||||
if (cpfLimpo.length !== 11) return false;
|
if (cpfLimpo.length !== 11) return false;
|
||||||
|
|
||||||
if (/^(\d)\1+$/.test(cpfLimpo)) return false;
|
if (/^(\d)\1+$/.test(cpfLimpo)) return false;
|
||||||
|
|
||||||
let soma = 0;
|
let soma = 0;
|
||||||
@ -51,6 +54,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const [avatarUrl, setAvatarUrl] = useState(null);
|
const [avatarUrl, setAvatarUrl] = useState(null);
|
||||||
const [showRequiredModal, setShowRequiredModal] = useState(false);
|
const [showRequiredModal, setShowRequiredModal] = useState(false);
|
||||||
const [emptyFields, setEmptyFields] = useState([]);
|
const [emptyFields, setEmptyFields] = useState([]);
|
||||||
@ -122,6 +126,13 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAvailabilityUpdate = useCallback(
|
||||||
|
(newAvailability) => {
|
||||||
|
setFormData((prev) => ({ ...prev, availability: newAvailability }));
|
||||||
|
},
|
||||||
|
[setFormData]
|
||||||
|
);
|
||||||
|
|
||||||
const handleCepBlur = async () => {
|
const handleCepBlur = async () => {
|
||||||
const cep = formData.cep?.replace(/\D/g, "");
|
const cep = formData.cep?.replace(/\D/g, "");
|
||||||
if (cep && cep.length === 8) {
|
if (cep && cep.length === 8) {
|
||||||
@ -200,6 +211,42 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
const handleCreateAvailability = async (newAvailability) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(ENDPOINT_AVAILABILITY, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(newAvailability),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("Disponibilidade criada :", data);
|
||||||
|
alert("Disponibilidade criada com sucesso!");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao criar disponibilidade:", error);
|
||||||
|
alert("Erro ao criar disponibilidade.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePatchAvailability = async (id, updatedAvailability) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${ENDPOINT_AVAILABILITY}?id=${id}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(updatedAvailability),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("Disponibilidade atualizada:", data);
|
||||||
|
alert("Disponibilidade atualizada com sucesso!");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao atualizar disponibilidade:", error);
|
||||||
|
alert("Erro ao atualizar disponibilidade.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const missingFields = [];
|
const missingFields = [];
|
||||||
@ -241,17 +288,33 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await onSave({ ...formData });
|
await onSave({ ...formData });
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
if (formData.availability && formData.availability.length > 0) {
|
||||||
|
if (formData.availabilityId) {
|
||||||
|
await handlePatchAvailability(
|
||||||
|
formData.availabilityId,
|
||||||
|
formData.availability
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await handleCreateAvailability(formData.availability);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alert("Médico salvo e disponibilidade enviada ao mock com sucesso!");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao salvar médico ou disponibilidade:", error);
|
||||||
|
alert("Erro ao salvar médico ou disponibilidade.");
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModalClose = () => {
|
const handleModalClose = () => {
|
||||||
setShowRequiredModal(false);
|
setShowRequiredModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/* Modal de Alerta */}
|
||||||
{showRequiredModal && (
|
{showRequiredModal && (
|
||||||
<div className="modal-overlay">
|
<div className="modal-overlay">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
@ -299,6 +362,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Formulário Principal */}
|
||||||
<div className="card doctor-form-container shadow-sm">
|
<div className="card doctor-form-container shadow-sm">
|
||||||
<h3 className="doctor-form-title">MediConnect</h3>
|
<h3 className="doctor-form-title">MediConnect</h3>
|
||||||
|
|
||||||
@ -319,6 +383,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="row mt-3">
|
<div className="row mt-3">
|
||||||
|
{/* Foto / Avatar */}
|
||||||
<div className="col-md-6 mb-3 avatar-container">
|
<div className="col-md-6 mb-3 avatar-container">
|
||||||
<div className="me-3">
|
<div className="me-3">
|
||||||
{avatarUrl ? (
|
{avatarUrl ? (
|
||||||
@ -354,6 +419,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Nome Completo */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label">Nome: *</label>
|
<label className="form-label">Nome: *</label>
|
||||||
<input
|
<input
|
||||||
@ -365,6 +431,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Data de Nascimento */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label">Data de nascimento:</label>
|
<label className="form-label">Data de nascimento:</label>
|
||||||
<input
|
<input
|
||||||
@ -377,6 +444,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
max="2025-09-24"
|
max="2025-09-24"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* CPF */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label">CPF: *</label>
|
<label className="form-label">CPF: *</label>
|
||||||
<input
|
<input
|
||||||
@ -399,6 +467,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Estado do CRM */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label">Estado do CRM: *</label>
|
<label className="form-label">Estado do CRM: *</label>
|
||||||
<select
|
<select
|
||||||
@ -438,6 +507,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* CRM */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label">CRM: *</label>
|
<label className="form-label">CRM: *</label>
|
||||||
<input
|
<input
|
||||||
@ -450,6 +520,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Especialização */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label">Especialização:</label>
|
<label className="form-label">Especialização:</label>
|
||||||
<select
|
<select
|
||||||
@ -497,6 +568,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
className={`collapse${collapsedSections.contato ? " show" : ""}`}
|
className={`collapse${collapsedSections.contato ? " show" : ""}`}
|
||||||
>
|
>
|
||||||
<div className="row mt-3">
|
<div className="row mt-3">
|
||||||
|
{/* Email */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label">Email: *</label>
|
<label className="form-label">Email: *</label>
|
||||||
<input
|
<input
|
||||||
@ -508,6 +580,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Telefone 1 (Principal) */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label">Telefone: *</label>
|
<label className="form-label">Telefone: *</label>
|
||||||
<input
|
<input
|
||||||
@ -519,6 +592,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Telefone 2 (Opcional) */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label">Telefone 2:</label>
|
<label className="form-label">Telefone 2:</label>
|
||||||
<input
|
<input
|
||||||
@ -548,6 +622,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
className={`collapse${collapsedSections.endereco ? " show" : ""}`}
|
className={`collapse${collapsedSections.endereco ? " show" : ""}`}
|
||||||
>
|
>
|
||||||
<div className="row mt-3">
|
<div className="row mt-3">
|
||||||
|
{/* CEP */}
|
||||||
<div className="col-md-4 mb-3">
|
<div className="col-md-4 mb-3">
|
||||||
<label className="form-label">CEP:</label>
|
<label className="form-label">CEP:</label>
|
||||||
<input
|
<input
|
||||||
@ -559,6 +634,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
onBlur={handleCepBlur}
|
onBlur={handleCepBlur}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Rua */}
|
||||||
<div className="col-md-8 mb-3">
|
<div className="col-md-8 mb-3">
|
||||||
<label className="form-label">Rua:</label>
|
<label className="form-label">Rua:</label>
|
||||||
<input
|
<input
|
||||||
@ -569,6 +645,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Bairro */}
|
||||||
<div className="col-md-6 mb-3">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label">Bairro:</label>
|
<label className="form-label">Bairro:</label>
|
||||||
<input
|
<input
|
||||||
@ -579,6 +656,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Cidade */}
|
||||||
<div className="col-md-4 mb-3">
|
<div className="col-md-4 mb-3">
|
||||||
<label className="form-label">Cidade:</label>
|
<label className="form-label">Cidade:</label>
|
||||||
<input
|
<input
|
||||||
@ -589,6 +667,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Estado */}
|
||||||
<div className="col-md-2 mb-3">
|
<div className="col-md-2 mb-3">
|
||||||
<label className="form-label">Estado:</label>
|
<label className="form-label">Estado:</label>
|
||||||
<input
|
<input
|
||||||
@ -599,6 +678,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Número */}
|
||||||
<div className="col-md-4 mb-3">
|
<div className="col-md-4 mb-3">
|
||||||
<label className="form-label">Número:</label>
|
<label className="form-label">Número:</label>
|
||||||
<input
|
<input
|
||||||
@ -609,6 +689,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Complemento */}
|
||||||
<div className="col-md-8 mb-3">
|
<div className="col-md-8 mb-3">
|
||||||
<label className="form-label">Complemento:</label>
|
<label className="form-label">Complemento:</label>
|
||||||
<input
|
<input
|
||||||
@ -629,23 +710,27 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
className="section-header"
|
className="section-header"
|
||||||
onClick={() => handleToggleCollapse("horarios")}
|
onClick={() => handleToggleCollapse("horarios")}
|
||||||
>
|
>
|
||||||
Horários
|
Horários de Atendimento
|
||||||
<span className="section-toggle">
|
<span className="section-toggle">
|
||||||
{collapsedSections.horarios ? "▲" : "▼"}
|
{collapsedSections.horarios ? "▲" : "▼"}
|
||||||
</span>
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
|
<div
|
||||||
<div className={`collapse${collapsedSections.horarios ? " show" : ""}`}>
|
className={`collapse${collapsedSections.horarios ? " show" : ""}`}
|
||||||
|
>
|
||||||
|
<div className="row mt-3">
|
||||||
|
<div className="col-12 mb-3">
|
||||||
|
<p className="form-label text-muted">
|
||||||
|
Defina seus horários de atendimento para cada dia da semana.
|
||||||
|
Marque um dia para começar a adicionar blocos de tempo.
|
||||||
|
</p>
|
||||||
<HorariosDisponibilidade
|
<HorariosDisponibilidade
|
||||||
onChange={(dados) => {
|
initialAvailability={formData.availability}
|
||||||
console.log("Disponibilidades atualizadas:", dados);
|
onUpdate={handleAvailabilityUpdate}
|
||||||
// Se quiser salvar no formData:
|
|
||||||
// setFormData(prev => ({ ...prev, disponibilidades: dados }));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* BOTÕES DE AÇÃO */}
|
{/* BOTÕES DE AÇÃO */}
|
||||||
@ -662,6 +747,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
<button className="btn btn-light btn-cancel">Cancelar</button>
|
<button className="btn btn-light btn-cancel">Cancelar</button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,106 +1,402 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
import { Clock } from "lucide-react";
|
||||||
|
|
||||||
const diasDaSemana = [
|
const initialBlockTemplate = {
|
||||||
"segunda",
|
id: null,
|
||||||
"terca",
|
inicio: "07:00",
|
||||||
"quarta",
|
termino: "18:00",
|
||||||
"quinta",
|
isNew: true,
|
||||||
"sexta",
|
};
|
||||||
"sabado",
|
|
||||||
"domingo"
|
const emptyAvailabilityTemplate = [
|
||||||
|
{ dia: "Segunda-feira", isChecked: false, blocos: [] },
|
||||||
|
{ dia: "Terça-feira", isChecked: false, blocos: [] },
|
||||||
|
{ dia: "Quarta-feira", isChecked: false, blocos: [] },
|
||||||
|
{ dia: "Quinta-feira", isChecked: false, blocos: [] },
|
||||||
|
{ dia: "Sexta-feira", isChecked: false, blocos: [] },
|
||||||
|
{ dia: "Sábado", isChecked: false, blocos: [] },
|
||||||
|
{ dia: "Domingo", isChecked: false, blocos: [] },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function HorariosDisponibilidade({ onChange }) {
|
const HorariosDisponibilidade = ({
|
||||||
const [disponibilidades, setDisponibilidades] = useState(
|
initialAvailability = emptyAvailabilityTemplate,
|
||||||
diasDaSemana.map(dia => ({
|
onUpdate,
|
||||||
weekday: dia,
|
}) => {
|
||||||
slots: [{ start_time: "09:00", end_time: "17:00" }],
|
const [availability, setAvailability] = useState(initialAvailability);
|
||||||
ativo: false,
|
|
||||||
}))
|
useEffect(() => {
|
||||||
|
if (initialAvailability !== emptyAvailabilityTemplate) {
|
||||||
|
setAvailability(initialAvailability);
|
||||||
|
}
|
||||||
|
}, [initialAvailability]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onUpdate) {
|
||||||
|
onUpdate(availability);
|
||||||
|
}
|
||||||
|
}, [availability, onUpdate]);
|
||||||
|
|
||||||
|
const handleDayCheck = useCallback((dayIndex, currentIsChecked) => {
|
||||||
|
const isChecked = !currentIsChecked;
|
||||||
|
|
||||||
|
setAvailability((prev) =>
|
||||||
|
prev.map((day, i) =>
|
||||||
|
i === dayIndex
|
||||||
|
? {
|
||||||
|
...day,
|
||||||
|
isChecked,
|
||||||
|
blocos: isChecked
|
||||||
|
? day.blocos.length === 0
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
...initialBlockTemplate,
|
||||||
|
id: Date.now() + Math.random(),
|
||||||
|
isNew: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: day.blocos
|
||||||
|
: [],
|
||||||
|
}
|
||||||
|
: day
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
function handleToggleDia(index) {
|
const handleAddBlock = useCallback((dayIndex) => {
|
||||||
const updated = [...disponibilidades];
|
const tempId = Date.now() + Math.random();
|
||||||
updated[index].ativo = !updated[index].ativo;
|
const newBlock = { ...initialBlockTemplate, id: tempId, isNew: true };
|
||||||
setDisponibilidades(updated);
|
|
||||||
onChange?.(updated);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSlotChange(indexDia, indexSlot, campo, valor) {
|
setAvailability((prev) =>
|
||||||
const updated = [...disponibilidades];
|
prev.map((day, i) =>
|
||||||
updated[indexDia].slots[indexSlot][campo] = valor;
|
i === dayIndex
|
||||||
setDisponibilidades(updated);
|
? {
|
||||||
onChange?.(updated);
|
...day,
|
||||||
|
blocos: [...day.blocos, newBlock],
|
||||||
|
isChecked: true,
|
||||||
}
|
}
|
||||||
|
: day
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
function handleAddSlot(indexDia) {
|
const handleRemoveBlock = useCallback((dayIndex, blockId) => {
|
||||||
const updated = [...disponibilidades];
|
setAvailability((prev) =>
|
||||||
updated[indexDia].slots.push({ start_time: "", end_time: "" });
|
prev.map((day, i) => {
|
||||||
setDisponibilidades(updated);
|
if (i === dayIndex) {
|
||||||
onChange?.(updated);
|
const newBlocos = day.blocos.filter((bloco) => bloco.id !== blockId);
|
||||||
|
return {
|
||||||
|
...day,
|
||||||
|
blocos: newBlocos,
|
||||||
|
isChecked: newBlocos.length > 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
return day;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
function handleRemoveSlot(indexDia, indexSlot) {
|
const handleTimeChange = useCallback((dayIndex, blockId, field, value) => {
|
||||||
const updated = [...disponibilidades];
|
setAvailability((prev) =>
|
||||||
updated[indexDia].slots.splice(indexSlot, 1);
|
prev.map((day, i) =>
|
||||||
setDisponibilidades(updated);
|
i === dayIndex
|
||||||
onChange?.(updated);
|
? {
|
||||||
|
...day,
|
||||||
|
blocos: day.blocos.map((bloco) =>
|
||||||
|
bloco.id === blockId ? { ...bloco, [field]: value } : bloco
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
: day
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderTimeBlock = (dayIndex, bloco) => (
|
||||||
|
<div
|
||||||
|
key={bloco.id}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: window.innerWidth < 640 ? "column" : "row",
|
||||||
|
alignItems: window.innerWidth < 640 ? "flex-start" : "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
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",
|
||||||
|
backgroundColor: bloco.isNew ? "#eef2ff" : "#ffffff",
|
||||||
|
border: bloco.isNew ? "2px solid #6366f1" : "1px solid #e5e7eb",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: window.innerWidth < 640 ? "column" : "row",
|
||||||
|
gap: window.innerWidth < 640 ? "0" : "12px",
|
||||||
|
width: window.innerWidth < 640 ? "100%" : "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "4px",
|
||||||
|
marginBottom: window.innerWidth < 640 ? "8px" : "0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
htmlFor={`inicio-${dayIndex}-${bloco.id}`}
|
||||||
|
style={{ fontWeight: 500, color: "#4b5563", width: "64px" }}
|
||||||
|
>
|
||||||
|
Início:
|
||||||
|
</label>
|
||||||
|
<div style={{ position: "relative" }}>
|
||||||
|
<input
|
||||||
|
id={`inicio-${dayIndex}-${bloco.id}`}
|
||||||
|
type="time"
|
||||||
|
value={bloco.inicio}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleTimeChange(dayIndex, bloco.id, "inicio", e.target.value)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
padding: "4px 6px",
|
||||||
|
border: "1px solid #d1d5db",
|
||||||
|
borderRadius: "6px",
|
||||||
|
width: "100%",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
outline: "none",
|
||||||
|
fontSize: "13px"
|
||||||
|
}}
|
||||||
|
step="300"
|
||||||
|
/>
|
||||||
|
<Clock
|
||||||
|
size={12}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
right: "8px",
|
||||||
|
top: "50%",
|
||||||
|
transform: "translateY(-50%)",
|
||||||
|
color: "#9ca3af",
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||||
|
<label
|
||||||
|
htmlFor={`termino-${dayIndex}-${bloco.id}`}
|
||||||
|
style={{ fontWeight: 500, color: "#4b5563", width: "56px", fontSize: "13px" }}
|
||||||
|
>
|
||||||
|
Término:
|
||||||
|
</label>
|
||||||
|
<div style={{ position: "relative" }}>
|
||||||
|
<input
|
||||||
|
id={`termino-${dayIndex}-${bloco.id}`}
|
||||||
|
type="time"
|
||||||
|
value={bloco.termino}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleTimeChange(dayIndex, bloco.id, "termino", e.target.value)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
padding: "4px 6px",
|
||||||
|
border: "1px solid #d1d5db",
|
||||||
|
borderRadius: "6px",
|
||||||
|
width: "100%",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
outline: "none",
|
||||||
|
fontSize: "13px",
|
||||||
|
}}
|
||||||
|
step="300"
|
||||||
|
/>
|
||||||
|
<Clock
|
||||||
|
size={12}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
right: "8px",
|
||||||
|
top: "50%",
|
||||||
|
transform: "translateY(-50%)",
|
||||||
|
color: "#9ca3af",
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => handleRemoveBlock(dayIndex, bloco.id)}
|
||||||
|
style={{
|
||||||
|
marginTop: window.innerWidth < 640 ? "8px" : "0",
|
||||||
|
padding: "4px 10px",
|
||||||
|
backgroundColor: "#ef4444",
|
||||||
|
color: "white",
|
||||||
|
fontWeight: 600,
|
||||||
|
borderRadius: "13px",
|
||||||
|
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
||||||
|
transition: "all 0.2s",
|
||||||
|
width: window.innerWidth < 640 ? "100%" : "auto",
|
||||||
|
cursor: "pointer",
|
||||||
|
border: "none",
|
||||||
|
opacity: 1,
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) =>
|
||||||
|
(e.currentTarget.style.backgroundColor = "#dc2626")
|
||||||
|
}
|
||||||
|
onMouseLeave={(e) =>
|
||||||
|
(e.currentTarget.style.backgroundColor = "#ef4444")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Remover Bloco
|
||||||
|
</button>
|
||||||
|
{bloco.isNew && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: "12px",
|
||||||
|
color: "#6366f1",
|
||||||
|
marginTop: "8px",
|
||||||
|
marginLeft: window.innerWidth < 640 ? "0" : "16px",
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div
|
||||||
{disponibilidades.map((dia, i) => (
|
style={{
|
||||||
<div key={dia.weekday} className="border p-3 rounded-md bg-gray-50">
|
maxWidth: "960px",
|
||||||
<div className="flex items-center justify-between">
|
margin: "0 auto",
|
||||||
<label className="font-semibold capitalize">
|
fontFamily: "Inter, sans-serif",
|
||||||
{dia.weekday}
|
}}
|
||||||
</label>
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
|
||||||
|
{availability.map((day, dayIndex) => {
|
||||||
|
const isChecked = day.isChecked;
|
||||||
|
|
||||||
|
const dayHeaderStyle = {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
padding: "12px 0",
|
||||||
|
borderBottom: "1px solid #e5e7eb",
|
||||||
|
marginBottom: "16px",
|
||||||
|
backgroundColor: isChecked ? "#1f2937" : "#f9fafb",
|
||||||
|
borderRadius: "8px",
|
||||||
|
paddingLeft: "16px",
|
||||||
|
paddingRight: "16px",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "background-color 0.2s",
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={day.dia}
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#f9fafb",
|
||||||
|
padding: "8px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
border: "1px solid #e5e7eb",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...dayHeaderStyle,
|
||||||
|
backgroundColor: isChecked ? "#1f2937" : "#f9fafb",
|
||||||
|
borderBottom: isChecked
|
||||||
|
? "1px solid #4b5563"
|
||||||
|
: "1px solid #e5e7eb",
|
||||||
|
}}
|
||||||
|
onClick={() => handleDayCheck(dayIndex, isChecked)}
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
fontSize: "18px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: isChecked ? "white" : "#1f2937",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "12px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{day.dia}</span>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={dia.ativo}
|
checked={isChecked}
|
||||||
onChange={() => handleToggleDia(i)}
|
onChange={() => {}}
|
||||||
|
style={{
|
||||||
|
width: "20px",
|
||||||
|
height: "20px",
|
||||||
|
accentColor: isChecked ? "#3b82f6" : "#9ca3af",
|
||||||
|
marginLeft: "8px",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{dia.ativo && (
|
{isChecked && (
|
||||||
<div className="mt-2 space-y-2">
|
<div style={{ marginTop: "16px" }}>
|
||||||
{dia.slots.map((slot, j) => (
|
{day.blocos.length === 0 && (
|
||||||
<div key={j} className="flex gap-2 items-center">
|
<p
|
||||||
<input
|
style={{
|
||||||
type="time"
|
color: "#6b7280",
|
||||||
value={slot.start_time}
|
fontStyle: "italic",
|
||||||
onChange={e => handleSlotChange(i, j, "start_time", e.target.value)}
|
marginBottom: "16px",
|
||||||
className="border rounded p-1"
|
}}
|
||||||
/>
|
|
||||||
<span>até</span>
|
|
||||||
<input
|
|
||||||
type="time"
|
|
||||||
value={slot.end_time}
|
|
||||||
onChange={e => handleSlotChange(i, j, "end_time", e.target.value)}
|
|
||||||
className="border rounded p-1"
|
|
||||||
/>
|
|
||||||
{dia.slots.length > 1 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleRemoveSlot(i, j)}
|
|
||||||
className="text-red-600 hover:underline"
|
|
||||||
>
|
>
|
||||||
Remover
|
Nenhum bloco de horário definido.
|
||||||
</button>
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "16px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{day.blocos.map((bloco) =>
|
||||||
|
renderTimeBlock(dayIndex, bloco)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
onClick={() => handleAddBlock(dayIndex)}
|
||||||
onClick={() => handleAddSlot(i)}
|
style={{
|
||||||
className="text-blue-600 hover:underline mt-2"
|
marginTop: "15px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: "10px 22px",
|
||||||
|
backgroundColor: "#10b981",
|
||||||
|
color: "white",
|
||||||
|
fontWeight: "bold",
|
||||||
|
borderRadius: "12px",
|
||||||
|
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
||||||
|
transition: "all 0.3s",
|
||||||
|
cursor: "pointer",
|
||||||
|
border: "none",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) =>
|
||||||
|
(e.currentTarget.style.backgroundColor = "#059669")
|
||||||
|
}
|
||||||
|
onMouseLeave={(e) =>
|
||||||
|
(e.currentTarget.style.backgroundColor = "#10b981")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
+ Adicionar pausa/bloco
|
+ Adicionar novo bloco
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HorariosDisponibilidade;
|
||||||
|
|||||||
@ -1,25 +1,17 @@
|
|||||||
import API_KEY from "../apiKeys";
|
import API_KEY from '../apiKeys';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const GetDoctorByID = async (ID, authHeader) => {
|
const GetDoctorByID = async (ID, authHeader) => {
|
||||||
|
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
myHeaders.append('apikey', API_KEY)
|
myHeaders.append('apikey', API_KEY);
|
||||||
myHeaders.append('Authorization', authHeader)
|
if (authHeader) myHeaders.append('Authorization', authHeader);
|
||||||
|
|
||||||
var requestOptions = {
|
const requestOptions = { method: 'GET', redirect: 'follow', headers: myHeaders };
|
||||||
method: 'GET',
|
const res = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${ID}`, requestOptions);
|
||||||
redirect: 'follow',
|
const DictMedico = await res.json();
|
||||||
headers:myHeaders
|
return DictMedico;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const result = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${ID}`, requestOptions)
|
|
||||||
const DictMedico = await result.json()
|
|
||||||
return DictMedico
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const GetAllDoctors = async (authHeader) => {
|
const GetAllDoctors = async (authHeader) => {
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
|
|||||||
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;
|
||||||
16
src/components/utils/fetchErros/ManagerFunction.js
Normal file
16
src/components/utils/fetchErros/ManagerFunction.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
function manager (setShowModal, RefreshingToken, setErrorInfo,errorData) {
|
||||||
|
|
||||||
|
console.log((errorData, "MANAGER"))
|
||||||
|
|
||||||
|
|
||||||
|
if(errorData.httpStatus === 401){
|
||||||
|
RefreshingToken()
|
||||||
|
}else{
|
||||||
|
|
||||||
|
// setErrorInfo(errorData)
|
||||||
|
setShowModal("modal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default manager
|
||||||
@ -24,7 +24,7 @@ return(
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
{showModal ?
|
{showModal === "modal"?
|
||||||
|
|
||||||
<div className="modal-overlay">
|
<div className="modal-overlay">
|
||||||
|
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
@ -14,6 +14,17 @@
|
|||||||
"icon": "hospital-fill",
|
"icon": "hospital-fill",
|
||||||
"url": "/admin/medicos"
|
"url": "/admin/medicos"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Agendamentos",
|
||||||
|
"icon": "calendar-plus-fill",
|
||||||
|
"url": "/admin/agendamento"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Relatórios",
|
||||||
|
"icon": "table",
|
||||||
|
"url": "/admin/laudo"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Gestão de Usuários",
|
"name": "Gestão de Usuários",
|
||||||
@ -25,12 +36,5 @@
|
|||||||
"name": "Painel Administrativo",
|
"name": "Painel Administrativo",
|
||||||
"icon": "file-bar-graph-fill",
|
"icon": "file-bar-graph-fill",
|
||||||
"url": "/admin/painel"
|
"url": "/admin/painel"
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Laudo do Paciente",
|
|
||||||
"icon": "table",
|
|
||||||
"url": "/admin/laudo"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
@ -6,14 +6,14 @@ import TabelaAgendamentoDia from '../components/AgendarConsulta/TabelaAgendament
|
|||||||
import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana';
|
import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana';
|
||||||
import TabelaAgendamentoMes from '../components/AgendarConsulta/TabelaAgendamentoMes';
|
import TabelaAgendamentoMes from '../components/AgendarConsulta/TabelaAgendamentoMes';
|
||||||
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta';
|
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta';
|
||||||
|
// Importação de endpoints para lógica da Fila de Espera e Médicos (versão main)
|
||||||
import { GetAllDoctors } from '../components/utils/Functions-Endpoints/Doctor.js';
|
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient.js';
|
||||||
|
import { GetAllDoctors, GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor.js';
|
||||||
|
|
||||||
import { useAuth } from '../components/utils/AuthProvider.js';
|
import { useAuth } from '../components/utils/AuthProvider.js';
|
||||||
// ✨ NOVO: Caminho de importação corrigido com base na sua estrutura de pastas
|
// ✨ NOVO: Caminho de importação corrigido com base na sua estrutura de pastas
|
||||||
import AgendamentosMes from '../components/AgendarConsulta/DadosConsultasMock.js';
|
import AgendamentosMes from '../components/AgendarConsulta/DadosConsultasMock.js';
|
||||||
|
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import "./style/Agendamento.css";
|
import "./style/Agendamento.css";
|
||||||
import './style/FilaEspera.css';
|
import './style/FilaEspera.css';
|
||||||
@ -21,9 +21,12 @@ import { Search } from 'lucide-react';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Agendamento = () => {
|
const Agendamento = ({setDictInfo}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
||||||
|
const [selectedID, setSelectedId] = useState('0')
|
||||||
|
const [filaEsperaData, setfilaEsperaData] = useState([])
|
||||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||||
const [tabela, setTabela] = useState('diario');
|
const [tabela, setTabela] = useState('diario');
|
||||||
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
||||||
@ -42,30 +45,69 @@ const Agendamento = () => {
|
|||||||
|
|
||||||
let authHeader = getAuthorizationHeader()
|
let authHeader = getAuthorizationHeader()
|
||||||
|
|
||||||
const FiltrarAgendamentos = (listaTodosAgendamentos) => {
|
|
||||||
let DictAgendamentosOrganizados = {};
|
|
||||||
|
|
||||||
for (let i = 0; i < listaTodosAgendamentos.length; i++) {
|
const FiltrarAgendamentos = async (listaTodosAgendamentos) => {
|
||||||
const agendamento = listaTodosAgendamentos[i];
|
const ConfigurarFiladeEspera = async (patient_id, doctor_id, agendamento) => {
|
||||||
|
|
||||||
|
let medico = await GetDoctorByID(doctor_id, authHeader);
|
||||||
|
let paciente = await GetPatientByID(patient_id, authHeader);
|
||||||
|
|
||||||
|
console.log(medico)
|
||||||
|
|
||||||
|
let dicionario = {
|
||||||
|
agendamento: 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);
|
||||||
|
} else {
|
||||||
const DiaAgendamento = agendamento.scheduled_at.split("T")[0];
|
const DiaAgendamento = agendamento.scheduled_at.split("T")[0];
|
||||||
|
|
||||||
//console.log(DictAgendamentosOrganizados)
|
|
||||||
|
|
||||||
if (DiaAgendamento in DictAgendamentosOrganizados) {
|
if (DiaAgendamento in DictAgendamentosOrganizados) {
|
||||||
// já existe a data → adiciona na lista
|
|
||||||
DictAgendamentosOrganizados[DiaAgendamento].push(agendamento);
|
DictAgendamentosOrganizados[DiaAgendamento].push(agendamento);
|
||||||
} else {
|
} else {
|
||||||
// não existe → cria nova key com uma lista
|
|
||||||
DictAgendamentosOrganizados[DiaAgendamento] = [agendamento];
|
DictAgendamentosOrganizados[DiaAgendamento] = [agendamento];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setAgendamentosOrganizados(DictAgendamentosOrganizados);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Requisição inicial para mostrar os agendamentos do banco de dados
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const chavesOrdenadas = Object.keys(DictAgendamentosOrganizados).sort((a, b) => {
|
||||||
|
if (a < b) return -1;
|
||||||
|
if (a > b) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
let DictAgendamentosFinal = {};
|
||||||
|
for (const data of chavesOrdenadas) {
|
||||||
|
DictAgendamentosFinal[data] = DictAgendamentosOrganizados[data];
|
||||||
|
}
|
||||||
|
setAgendamentosOrganizados(DictAgendamentosFinal);
|
||||||
|
setfilaEsperaData(ListaFilaDeEspera);
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
myHeaders.append("Authorization", authHeader);
|
myHeaders.append("Authorization", authHeader);
|
||||||
@ -79,14 +121,13 @@ const Agendamento = () => {
|
|||||||
|
|
||||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions)
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(result => {FiltrarAgendamentos(result);})
|
.then(result => {FiltrarAgendamentos(result);console.log(result)})
|
||||||
.catch(error => console.log('error', error));
|
.catch(error => console.log('error', error));
|
||||||
|
|
||||||
const PegarTodosOsMedicos = async () => {
|
const PegarTodosOsMedicos = async () => {
|
||||||
let lista = []
|
let lista = []
|
||||||
const TodosOsMedicos = await GetAllDoctors(authHeader)
|
const TodosOsMedicos = await GetAllDoctors(authHeader)
|
||||||
|
|
||||||
//console.log(TodosOsMedicos, "tentativa")
|
|
||||||
for(let d = 0; TodosOsMedicos.length > d; d++){
|
for(let d = 0; TodosOsMedicos.length > d; d++){
|
||||||
lista.push({nomeMedico: TodosOsMedicos[d].full_name, idMedico: TodosOsMedicos[d].id })}
|
lista.push({nomeMedico: TodosOsMedicos[d].full_name, idMedico: TodosOsMedicos[d].id })}
|
||||||
setListaDeMedicos(lista)
|
setListaDeMedicos(lista)
|
||||||
@ -94,61 +135,53 @@ const Agendamento = () => {
|
|||||||
PegarTodosOsMedicos()
|
PegarTodosOsMedicos()
|
||||||
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
console.log("mudou FiltredTodosMedicos:", FiltredTodosMedicos);
|
console.log("mudou FiltredTodosMedicos:", FiltredTodosMedicos);
|
||||||
|
|
||||||
if (FiltredTodosMedicos.length === 1) {
|
if (FiltredTodosMedicos.length === 1) {
|
||||||
|
|
||||||
const unicoMedico = FiltredTodosMedicos[0];
|
const unicoMedico = FiltredTodosMedicos[0];
|
||||||
console.log(unicoMedico)
|
console.log(unicoMedico)
|
||||||
|
|
||||||
const idMedicoFiltrado = unicoMedico.idMedico;
|
const idMedicoFiltrado = unicoMedico.idMedico;
|
||||||
|
|
||||||
console.log(`Médico único encontrado: ${unicoMedico.nomeMedico}. ID: ${idMedicoFiltrado}`);
|
console.log(`Médico único encontrado: ${unicoMedico.nomeMedico}. ID: ${idMedicoFiltrado}`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const agendamentosDoMedico = filtrarAgendamentosPorMedico(
|
const agendamentosDoMedico = filtrarAgendamentosPorMedico(
|
||||||
DictAgendamentosOrganizados,
|
DictAgendamentosOrganizados,
|
||||||
idMedicoFiltrado
|
idMedicoFiltrado
|
||||||
);
|
);
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
console.log(`Total de agendamentos filtrados para este médico: ${agendamentosDoMedico.length}`);
|
console.log(`Total de agendamentos filtrados para este médico: ${agendamentosDoMedico.length}`);
|
||||||
console.log("Lista completa de Agendamentos do Médico:", agendamentosDoMedico);
|
console.log("Lista completa de Agendamentos do Médico:", agendamentosDoMedico);
|
||||||
FiltrarAgendamentos(agendamentosDoMedico)
|
FiltrarAgendamentos(agendamentosDoMedico)
|
||||||
|
|
||||||
|
|
||||||
// AQUI VOCÊ PODE APLICAR SUA LÓGICA FINAL:
|
|
||||||
// Ex: setar um novo estado com os agendamentos filtrados, se for necessário:
|
|
||||||
// setAgendamentosFiltrados(agendamentosDoMedico);
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Opcional: Limpar a lista filtrada se a busca não for mais única
|
|
||||||
// setAgendamentosFiltrados([]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [FiltredTodosMedicos]);
|
}, [FiltredTodosMedicos]);
|
||||||
|
|
||||||
|
const deleteConsulta = (selectedPatientId) => {
|
||||||
|
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"
|
||||||
* Filtra todos os agendamentos em um objeto aninhado (data -> [agendamentos])
|
});
|
||||||
* com base no ID do médico.
|
|
||||||
*
|
|
||||||
* @param {Object} dictAgendamentos - O dicionário de agendamentos.
|
var requestOptions = {
|
||||||
* @param {string} idMedicoFiltrado - O ID do médico (doctor_id) para ser usado como filtro.
|
method: 'PATCH',
|
||||||
* @returns {Array} Um array contendo todos os agendamentos que correspondem ao idMedicoFiltrado.
|
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 filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
||||||
|
|
||||||
// O corpo da função deve usar esses nomes de variáveis:
|
|
||||||
const todasAsListasDeAgendamentos = Object.values(dictAgendamentos);
|
const todasAsListasDeAgendamentos = Object.values(dictAgendamentos);
|
||||||
|
|
||||||
const todosOsAgendamentos = todasAsListasDeAgendamentos.flat();
|
const todosOsAgendamentos = todasAsListasDeAgendamentos.flat();
|
||||||
|
|
||||||
const agendamentosFiltrados = todosOsAgendamentos.filter(agendamento =>
|
const agendamentosFiltrados = todosOsAgendamentos.filter(agendamento =>
|
||||||
@ -159,29 +192,6 @@ const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Dados da fila de espera (sem alteração)
|
|
||||||
const filaEsperaData = [
|
|
||||||
{ nome: 'Ricardo Pereira', email: 'ricardo.pereira@gmail.com', cpf: '444.777.666-55', telefone: '(79) 99123-4567', entrada: '25/09/2025 às 08:00' },
|
|
||||||
{ nome: 'Ana Costa', email: 'ana.costa@gmail.com', cpf: '321.654.987-00', telefone: '(79) 97777-3333', entrada: '25/09/2025 às 08:30' },
|
|
||||||
{ nome: 'Lucas Martins', email: 'lucas.martins@gmail.com', cpf: '777.666.555-33', telefone: '(79) 99654-3210', entrada: '25/09/2025 às 09:00' },
|
|
||||||
{ nome: 'João Souza', email: 'joao.souza@gmail.com', cpf: '987.654.321-00', telefone: '(79) 98888-2222', entrada: '25/09/2025 às 14:00' },
|
|
||||||
{ nome: 'Maria Silva', email: 'maria.silva@gmail.com', cpf: '123.456.789-00', telefone: '(79) 99999-1111', entrada: '25/09/2025 às 14:30' },
|
|
||||||
{ nome: 'Fernanda Lima', email: 'fernanda.lima@gmail.com', cpf: '888.999.000-22', telefone: '(79) 98877-6655', entrada: '26/09/2025 às 09:30' },
|
|
||||||
{ nome: 'Carlos Andrade', email: 'carlos.andrade@gmail.com', cpf: '222.555.888-11', telefone: '(79) 99876-5432', entrada: '26/09/2025 às 10:00' },
|
|
||||||
{ nome: 'Juliana Oliveira', email: 'juliana.o@gmail.com', cpf: '111.222.333-44', telefone: '(79) 98765-1234', entrada: '26/09/2025 às 11:30' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Filtro da fila de espera (sem alteração)
|
|
||||||
const filteredFila = filaEsperaData.filter(item =>
|
|
||||||
item.nome.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
item.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
item.cpf.includes(searchTerm) ||
|
|
||||||
item.telefone.includes(searchTerm)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Lógica para filtrar os dados da AGENDA (AgendamentosMes)
|
|
||||||
const filteredAgendamentos = useMemo(() => {
|
const filteredAgendamentos = useMemo(() => {
|
||||||
if (!searchTerm.trim()) {
|
if (!searchTerm.trim()) {
|
||||||
return AgendamentosMes;
|
return AgendamentosMes;
|
||||||
@ -221,6 +231,7 @@ const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
|||||||
return ListaDiasDatas
|
return ListaDiasDatas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const handleClickAgendamento = (agendamento) => {
|
const handleClickAgendamento = (agendamento) => {
|
||||||
if (agendamento.status !== 'vazio') return
|
if (agendamento.status !== 'vazio') return
|
||||||
else setPageConsulta(true)
|
else setPageConsulta(true)
|
||||||
@ -234,7 +245,6 @@ const handleSearchMedicos = (term) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lógica simples de filtragem:
|
|
||||||
const filtered = ListaDeMedicos.filter(medico =>
|
const filtered = ListaDeMedicos.filter(medico =>
|
||||||
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
||||||
);
|
);
|
||||||
@ -248,18 +258,33 @@ const handleSearchMedicos = (term) => {
|
|||||||
<div>
|
<div>
|
||||||
<h1>Agendar nova consulta</h1>
|
<h1>Agendar nova consulta</h1>
|
||||||
|
|
||||||
<button className='manage-button btn' onClick={() => navigate('/secretaria/excecoes-disponibilidade')}>
|
|
||||||
|
<div className="btns-gerenciamento-e-consulta" style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
|
||||||
|
<button className="btn btn-primary" onClick={() => setPageConsulta(true)}>
|
||||||
|
<i className="bi bi-plus-circle"></i> Adicionar Consulta
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="manage-button btn"
|
||||||
|
onClick={() => navigate("/secretaria/excecoes-disponibilidade")}
|
||||||
|
>
|
||||||
|
<i className="bi bi-gear-fill me-1"></i>
|
||||||
|
Gerenciar Exceções
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className='manage-button btn' onClick={() => navigate('/secretaria/disponibilidade')}>
|
||||||
<i className="bi bi-gear-fill me-1"></i>
|
<i className="bi bi-gear-fill me-1"></i>
|
||||||
Mudar Disponibilidade
|
Mudar Disponibilidade
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{!PageNovaConsulta ? (
|
{!PageNovaConsulta ? (
|
||||||
<div className='atendimento-eprocura'>
|
<div className='atendimento-eprocura'>
|
||||||
|
<div className='unidade-selecionarprofissional'>
|
||||||
|
|
||||||
|
{/* Bloco de busca por médico */}
|
||||||
<div className='busca-atendimento-container'>
|
<div className='busca-atendimento-container'>
|
||||||
|
|
||||||
<div className='input-e-dropdown-wrapper'>
|
<div className='input-e-dropdown-wrapper'>
|
||||||
|
|
||||||
<div className='busca-atendimento'>
|
<div className='busca-atendimento'>
|
||||||
<div>
|
<div>
|
||||||
<i className="fa-solid fa-calendar-day"></i>
|
<i className="fa-solid fa-calendar-day"></i>
|
||||||
@ -267,7 +292,7 @@ const handleSearchMedicos = (term) => {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Filtrar atendimento por médico..."
|
placeholder="Filtrar atendimento por médico..."
|
||||||
value={searchTermDoctor}
|
value={searchTermDoctor}
|
||||||
onChange={(e) => handleSearchMedicos(e.target.value)} // Chama a nova função de filtro
|
onChange={(e) => handleSearchMedicos(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -280,10 +305,7 @@ const handleSearchMedicos = (term) => {
|
|||||||
key={medico.id}
|
key={medico.id}
|
||||||
className='dropdown-item'
|
className='dropdown-item'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// Ação ao selecionar o médico
|
setSearchTermDoctor(medico.nomeMedico);
|
||||||
setSearchTermDoctor(medico.nomeMedico); // Preenche o input
|
|
||||||
//setFiltredTodosMedicos([]); // Fecha o dropdown
|
|
||||||
// Lógica adicional, como selecionar o ID do médico...
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>{medico.nomeMedico} </p>
|
<p>{medico.nomeMedico} </p>
|
||||||
@ -294,15 +316,6 @@ const handleSearchMedicos = (term) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className='unidade-selecionarprofissional'>
|
|
||||||
<select>
|
|
||||||
<option value="" disabled selected >Unidade</option>
|
|
||||||
<option value="">Unidade Central</option>
|
|
||||||
<option value="">Unidade Zona Norte</option>
|
|
||||||
<option value="">Unidade Zona Oeste</option>
|
|
||||||
</select>
|
|
||||||
<input type="text" placeholder='Selecionar profissional' />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='container-btns-agenda-fila_esepera'>
|
<div className='container-btns-agenda-fila_esepera'>
|
||||||
@ -351,9 +364,10 @@ const handleSearchMedicos = (term) => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{tabela === "diario" && <TabelaAgendamentoDia handleClickAgendamento={handleClickAgendamento} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} />}
|
{/* Componentes de Tabela - Adicionado props de delete da main */}
|
||||||
{tabela === 'semanal' && <TabelaAgendamentoSemana agendamentos={DictAgendamentosOrganizados} ListarDiasdoMes={ListarDiasdoMes}/>}
|
{tabela === "diario" && <TabelaAgendamentoDia handleClickAgendamento={handleClickAgendamento} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />}
|
||||||
{tabela === 'mensal' && <TabelaAgendamentoMes ListarDiasdoMes={ListarDiasdoMes} aplicarCores={true} agendamentos={DictAgendamentosOrganizados} />}
|
{tabela === 'semanal' && <TabelaAgendamentoSemana agendamentos={DictAgendamentosOrganizados} ListarDiasdoMes={ListarDiasdoMes} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>}
|
||||||
|
{tabela === 'mensal' && <TabelaAgendamentoMes ListarDiasdoMes={ListarDiasdoMes} aplicarCores={true} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -373,21 +387,43 @@ const handleSearchMedicos = (term) => {
|
|||||||
<table className="fila-tabela">
|
<table className="fila-tabela">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Nome</th>
|
<th>Nome do Paciente</th> {/* Ajustado o cabeçalho */}
|
||||||
<th>Email</th>
|
<th>CPF</th> {/* Ajustado o cabeçalho */}
|
||||||
<th>CPF</th>
|
<th>Médico Solicitado</th> {/* Ajustado o cabeçalho */}
|
||||||
<th>Telefone</th>
|
<th>Data da Solicitação</th> {/* Ajustado o cabeçalho */}
|
||||||
<th>Entrou na fila de espera</th>
|
<th>Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{filteredFila.map((item, index) => (
|
{filaEsperaData.map((item, index) => (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td>{item.nome}</td>
|
<td> <p>{item.Infos?.paciente_nome} </p> </td>
|
||||||
<td>{item.email}</td>
|
<td><p>{item.Infos?.paciente_cpf} </p></td>
|
||||||
<td>{item.cpf}</td>
|
<td><p>{item.Infos?.nome_medico} </p></td>
|
||||||
<td>{item.telefone}</td>
|
<td>{dayjs(item.agendamento.created_at).format('DD/MM/YYYY HH:mm')}</td>
|
||||||
<td>{item.entrada}</td>
|
<td> <div className="d-flex gap-2">
|
||||||
|
|
||||||
|
<button className="btn btn-sm btn-edit"
|
||||||
|
onClick={() => {
|
||||||
|
console.log(item, 'item')
|
||||||
|
navigate(`${2}/edit`)
|
||||||
|
setDictInfo(item)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="bi bi-pencil me-1"></i> Editar
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-delete"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.agendamento.id)
|
||||||
|
setShowDeleteModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="bi bi-trash me-1"></i> Excluir
|
||||||
|
</button>
|
||||||
|
</div></td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -398,9 +434,10 @@ const handleSearchMedicos = (term) => {
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<AgendamentoCadastroManager />
|
<AgendamentoCadastroManager setPageConsulta={setPageConsulta} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Modal de Confirmação de Exclusão */}
|
||||||
{showDeleteModal && (
|
{showDeleteModal && (
|
||||||
<div
|
<div
|
||||||
className="modal fade show"
|
className="modal fade show"
|
||||||
@ -429,7 +466,7 @@ const handleSearchMedicos = (term) => {
|
|||||||
|
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
<p className="mb-0 fs-5">
|
<p className="mb-0 fs-5">
|
||||||
Tem certeza que deseja excluir este paciente?
|
Tem certeza que deseja excluir este agendamento?
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -447,7 +484,8 @@ const handleSearchMedicos = (term) => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-danger"
|
className="btn btn-danger"
|
||||||
//onClick={() => deletePatient(selectedPatientId)}
|
onClick={() => {deleteConsulta(selectedID);setShowDeleteModal(false)}}
|
||||||
|
|
||||||
>
|
>
|
||||||
<i className="bi bi-trash me-1"></i> Excluir
|
<i className="bi bi-trash me-1"></i> Excluir
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -2,17 +2,33 @@ import React from 'react'
|
|||||||
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta'
|
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta'
|
||||||
import API_KEY from '../components/utils/apiKeys'
|
import API_KEY from '../components/utils/apiKeys'
|
||||||
import { useAuth } from '../components/utils/AuthProvider'
|
import { useAuth } from '../components/utils/AuthProvider'
|
||||||
import { useState } from 'react'
|
import { useEffect,useState } from 'react'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { UserInfos } from '../components/utils/Functions-Endpoints/General'
|
||||||
const AgendamentoCadastroManager = () => {
|
const AgendamentoCadastroManager = ({setPageConsulta}) => {
|
||||||
|
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const {getAuthorizationHeader} = useAuth()
|
||||||
const [agendamento, setAgendamento] = useState({})
|
const [agendamento, setAgendamento] = useState({status:'confirmed'})
|
||||||
|
const [idUsuario, setIDusuario] = useState('0')
|
||||||
|
|
||||||
let authHeader = getAuthorizationHeader()
|
let authHeader = getAuthorizationHeader()
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const ColherInfoUsuario =async () => {
|
||||||
|
const result = await UserInfos(authHeader)
|
||||||
|
|
||||||
|
setIDusuario(result?.profile?.id)
|
||||||
|
|
||||||
|
}
|
||||||
|
ColherInfoUsuario()
|
||||||
|
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSave = (Dict) => {
|
const handleSave = (Dict) => {
|
||||||
let DataAtual = dayjs()
|
let DataAtual = dayjs()
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
@ -23,13 +39,14 @@ const AgendamentoCadastroManager = () => {
|
|||||||
var raw = JSON.stringify({
|
var raw = JSON.stringify({
|
||||||
"patient_id": Dict.patient_id,
|
"patient_id": Dict.patient_id,
|
||||||
"doctor_id": Dict.doctor_id,
|
"doctor_id": Dict.doctor_id,
|
||||||
"scheduled_at": DataAtual,
|
"scheduled_at": `${Dict.dataAtendimento}T${Dict.horarioInicio}:00.000Z`,
|
||||||
"duration_minutes": 30,
|
"duration_minutes": 30,
|
||||||
"appointment_type": "presencial",
|
"appointment_type": Dict.tipo_consulta,
|
||||||
"chief_complaint": "Dor de cabeça há 3 ",
|
|
||||||
"patient_notes": "Prefiro horário pela manhã",
|
"patient_notes": "Prefiro horário pela manhã",
|
||||||
"insurance_provider": "Unimed",
|
"insurance_provider": Dict.convenio,
|
||||||
"created_by": "87f2662c-9da7-45c0-9e05-521d9d92d105"
|
"status": Dict.status,
|
||||||
|
"created_by": idUsuario
|
||||||
});
|
});
|
||||||
|
|
||||||
var requestOptions = {
|
var requestOptions = {
|
||||||
@ -49,7 +66,7 @@ const AgendamentoCadastroManager = () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<FormNovaConsulta onSave={handleSave} agendamento={agendamento} setAgendamento={setAgendamento}/>
|
<FormNovaConsulta onSave={handleSave} agendamento={agendamento} setAgendamento={setAgendamento} onCancel={() => setPageConsulta(false)}/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,22 +1,43 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta'
|
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta'
|
||||||
import { useState } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import API_KEY from '../components/utils/apiKeys'
|
import API_KEY from '../components/utils/apiKeys'
|
||||||
import { useAuth } from '../components/utils/AuthProvider'
|
import { useAuth } from '../components/utils/AuthProvider'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { UserInfos } from '../components/utils/Functions-Endpoints/General'
|
||||||
|
|
||||||
|
const AgendamentoEditPage = ({setDictInfo, DictInfo}) => {
|
||||||
|
|
||||||
const AgendamentoEditPage = () => {
|
const [idUsuario, setIDusuario] = useState('0')
|
||||||
|
//let DataAtual = dayjs()
|
||||||
let DataAtual = dayjs()
|
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const {getAuthorizationHeader} = useAuth()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const [PatientToPatch, setPatientToPatch] = useState({})
|
const [PatientToPatch, setPatientToPatch] = useState({})
|
||||||
|
|
||||||
let id = params.id
|
let id = params.id
|
||||||
|
|
||||||
console.log(id)
|
console.log(DictInfo, "DENTRO DO EDITAR")
|
||||||
|
|
||||||
|
//console.log(DictInfo, 'aqui')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDictInfo({...DictInfo?.Infos,...DictInfo?.agendamento})
|
||||||
|
|
||||||
|
|
||||||
|
const ColherInfoUsuario =async () => {
|
||||||
|
const result = await UserInfos(authHeader)
|
||||||
|
|
||||||
|
setIDusuario(result?.profile?.id)
|
||||||
|
|
||||||
|
}
|
||||||
|
ColherInfoUsuario()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let authHeader = getAuthorizationHeader()
|
let authHeader = getAuthorizationHeader()
|
||||||
|
|
||||||
@ -30,13 +51,21 @@ const AgendamentoEditPage = () => {
|
|||||||
|
|
||||||
var raw = JSON.stringify({"patient_id": DictParaPatch.patient_id,
|
var raw = JSON.stringify({"patient_id": DictParaPatch.patient_id,
|
||||||
"doctor_id": DictParaPatch.doctor_id,
|
"doctor_id": DictParaPatch.doctor_id,
|
||||||
"scheduled_at": DataAtual,
|
|
||||||
"duration_minutes": 30,
|
"duration_minutes": 30,
|
||||||
"appointment_type": "presencial",
|
|
||||||
"chief_complaint": "Dor de cabeça há 3 ",
|
"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ã",
|
"patient_notes": "Prefiro horário pela manhã",
|
||||||
"insurance_provider": "Unimed",
|
"insurance_provider": DictParaPatch.convenio,
|
||||||
"created_by": "87f2662c-9da7-45c0-9e05-521d9d92d105"
|
"status": DictParaPatch.status,
|
||||||
|
"created_by": idUsuario
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -51,7 +80,7 @@ const AgendamentoEditPage = () => {
|
|||||||
redirect: 'follow'
|
redirect: 'follow'
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${id}`, requestOptions)
|
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${DictInfo.id}`, requestOptions)
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(result => console.log(result))
|
.then(result => console.log(result))
|
||||||
.catch(error => console.log('error', error));
|
.catch(error => console.log('error', error));
|
||||||
@ -60,7 +89,7 @@ const AgendamentoEditPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FormNovaConsulta onSave={handleSave} agendamento={PatientToPatch} setAgendamento={setPatientToPatch}/>
|
<FormNovaConsulta onSave={handleSave} agendamento={DictInfo} setAgendamento={setDictInfo}/>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
187
src/pages/DisponibilidadesDoctorPage.jsx
Normal file
187
src/pages/DisponibilidadesDoctorPage.jsx
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
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 [filtroMedicoNome, setFiltroMedicoNome] = useState("");
|
||||||
|
|
||||||
|
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);
|
||||||
|
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();
|
||||||
|
|
||||||
|
let dados = Array.isArray(result) ? result : [];
|
||||||
|
|
||||||
|
setDisponibilidades(dados);
|
||||||
|
setMedicoEncontradoId(doctorId);
|
||||||
|
} catch (error) {
|
||||||
|
setDisponibilidades([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (filtroMedicoNome) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
fetchDisponibilidades(filtroMedicoNome);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
} else {
|
||||||
|
setDisponibilidades([]);
|
||||||
|
setMedicoEncontradoId(null);
|
||||||
|
}
|
||||||
|
}, [filtroMedicoNome, fetchDisponibilidades]);
|
||||||
|
|
||||||
|
const rotaGerenciar = medicoEncontradoId
|
||||||
|
? `../medicos/${medicoEncontradoId}/edit`
|
||||||
|
: `../medicos/novo/edit`;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="main-content">
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
|
<h1 style={{ fontSize: "1.5rem", fontWeight: "bold", color: "#333" }}>
|
||||||
|
Disponibilidades por Médico
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
to={rotaGerenciar}
|
||||||
|
className="btn-primary"
|
||||||
|
style={{
|
||||||
|
padding: "10px 20px",
|
||||||
|
fontSize: "14px",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textDecoration: "none",
|
||||||
|
display: "inline-block",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+ Gerenciar Disponibilidades
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="atendimento-eprocura">
|
||||||
|
<div className="busca-atendimento">
|
||||||
|
<div style={{ marginRight: '10px' }}>
|
||||||
|
<i className="fa-solid fa-user-doctor"></i>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
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>
|
||||||
|
|
||||||
|
<section className="calendario-ou-filaespera">
|
||||||
|
<div className="fila-container">
|
||||||
|
<h2 className="fila-titulo">
|
||||||
|
Disponibilidades Encontradas ({disponibilidades.length})
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<p className="text-center py-10">Carregando disponibilidades...</p>
|
||||||
|
) : (filtroMedicoNome && disponibilidades.length === 0) ? (
|
||||||
|
<p className="text-center py-10">
|
||||||
|
Nenhuma disponibilidade encontrada para o nome buscado.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<table className="fila-tabela" style={{ width: "100%", borderCollapse: "collapse" }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{[ "Dia da Semana", "Início", "Término", "Intervalo", "Tipo Consulta"].map(
|
||||||
|
(header) => (
|
||||||
|
<th
|
||||||
|
key={header}
|
||||||
|
style={{ padding: "10px", borderBottom: "2px solid #ddd", textAlign: "left" }}
|
||||||
|
>
|
||||||
|
{header}
|
||||||
|
</th>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{disponibilidades.map((disp, index) => (
|
||||||
|
<tr key={disp.id || index} style={{ borderBottom: "1px solid #eee" }}>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DisponibilidadesDoctorPage;
|
||||||
@ -21,10 +21,11 @@ function DoctorCadastroManager() {
|
|||||||
const authHeader = getAuthorizationHeader();
|
const authHeader = getAuthorizationHeader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log(authHeader)
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
myHeaders.append("Content-Type", "application/json");
|
myHeaders.append("Content-Type", "application/json");
|
||||||
myHeaders.append("apikey", API_KEY);
|
myHeaders.append("apikey", API_KEY);
|
||||||
myHeaders.append("Authorization", authHeader);
|
myHeaders.append("Authorization",`Bearer ${authHeader.split(` `)[1]}` );
|
||||||
|
|
||||||
console.log('Dados recebidos do Form:', doctorData);
|
console.log('Dados recebidos do Form:', doctorData);
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ function DoctorCadastroManager() {
|
|||||||
redirect: 'follow'
|
redirect: 'follow'
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions);
|
const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/create-doctor", requestOptions);
|
||||||
|
|
||||||
console.log("Status da resposta:", response.status);
|
console.log("Status da resposta:", response.status);
|
||||||
console.log("Response ok:", response.ok);
|
console.log("Response ok:", response.ok);
|
||||||
|
|||||||
@ -1,77 +1,146 @@
|
|||||||
import React from 'react'
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor'
|
import { useParams, useSearchParams } from "react-router-dom";
|
||||||
import DoctorForm from '../components/doctors/DoctorForm'
|
import { GetDoctorByID } from "../components/utils/Functions-Endpoints/Doctor";
|
||||||
import { useAuth } from '../components/utils/AuthProvider'
|
import DoctorForm from "../components/doctors/DoctorForm";
|
||||||
import {useEffect, useState} from 'react'
|
import { useAuth } from "../components/utils/AuthProvider";
|
||||||
import { useParams } from 'react-router-dom'
|
import API_KEY from "../components/utils/apiKeys";
|
||||||
import API_KEY from '../components/utils/apiKeys'
|
|
||||||
|
const ENDPOINT_AVAILABILITY =
|
||||||
|
"https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_availability";
|
||||||
|
|
||||||
const DoctorEditPage = () => {
|
const DoctorEditPage = () => {
|
||||||
const {getAuthorizationHeader, isAuthenticated} = useAuth();
|
const { getAuthorizationHeader } = useAuth();
|
||||||
const [DoctorToPUT, setDoctorPUT] = useState({})
|
const [DoctorToPUT, setDoctorPUT] = useState({});
|
||||||
|
|
||||||
const Parametros = useParams()
|
const Parametros = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const DoctorID = Parametros.id;
|
||||||
|
const availabilityId = searchParams.get("availabilityId");
|
||||||
|
|
||||||
const DoctorID = Parametros.id
|
const [availabilityToPATCH, setAvailabilityToPATCH] = useState(null);
|
||||||
|
const [mode, setMode] = useState("doctor");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const authHeader = getAuthorizationHeader();
|
||||||
|
|
||||||
const authHeader = getAuthorizationHeader()
|
if (availabilityId) {
|
||||||
|
setMode("availability");
|
||||||
|
|
||||||
|
fetch(`${ENDPOINT_AVAILABILITY}?id=eq.${availabilityId}&select=*`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
apikey: API_KEY,
|
||||||
|
Authorization: authHeader,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
setAvailabilityToPATCH(data[0]);
|
||||||
|
console.log("Disponibilidade vinda da API:", data[0]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => console.error("Erro ao buscar disponibilidade:", err));
|
||||||
|
} else {
|
||||||
|
setMode("doctor");
|
||||||
GetDoctorByID(DoctorID, authHeader)
|
GetDoctorByID(DoctorID, authHeader)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log(data, "médico vindo da API");
|
console.log(data, "médico vindo da API");
|
||||||
setDoctorPUT(data[0])
|
setDoctorPUT(data[0]);
|
||||||
; // supabase retorna array
|
|
||||||
})
|
})
|
||||||
.catch((err) => console.error("Erro ao buscar paciente:", err));
|
.catch((err) => console.error("Erro ao buscar paciente:", err));
|
||||||
|
}
|
||||||
|
}, [DoctorID, availabilityId, getAuthorizationHeader]);
|
||||||
|
|
||||||
|
|
||||||
}, [])
|
|
||||||
const HandlePutDoctor = async () => {
|
const HandlePutDoctor = async () => {
|
||||||
const authHeader = getAuthorizationHeader()
|
const authHeader = getAuthorizationHeader();
|
||||||
|
|
||||||
|
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
myHeaders.append('apikey', API_KEY)
|
myHeaders.append("apikey", API_KEY);
|
||||||
myHeaders.append("Authorization", authHeader);
|
myHeaders.append("Authorization", authHeader);
|
||||||
myHeaders.append("Content-Type", "application/json");
|
myHeaders.append("Content-Type", "application/json");
|
||||||
|
|
||||||
var raw = JSON.stringify(DoctorToPUT);
|
var raw = JSON.stringify(DoctorToPUT);
|
||||||
|
|
||||||
console.log("Enviando médico para atualização:", DoctorToPUT);
|
console.log("Enviando médico para atualização (PUT):", DoctorToPUT);
|
||||||
|
|
||||||
var requestOptions = {
|
var requestOptions = {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers: myHeaders,
|
headers: myHeaders,
|
||||||
body: raw,
|
body: raw,
|
||||||
redirect: 'follow'
|
redirect: "follow",
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`,requestOptions);
|
const response = await fetch(
|
||||||
console.log(response)
|
`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`,
|
||||||
|
requestOptions
|
||||||
|
);
|
||||||
|
console.log("Resposta PUT Doutor:", response);
|
||||||
|
alert("Dados do médico atualizados com sucesso!");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erro ao atualizar paciente:", error);
|
console.error("Erro ao atualizar médico:", error);
|
||||||
|
alert("Erro ao atualizar dados do médico.");
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Função para Atualizar DISPONIBILIDADE (PATCH)
|
||||||
|
const HandlePatchAvailability = async (data) => {
|
||||||
|
const authHeader = getAuthorizationHeader();
|
||||||
|
|
||||||
|
var myHeaders = new Headers();
|
||||||
|
myHeaders.append("apikey", API_KEY);
|
||||||
|
myHeaders.append("Authorization", authHeader);
|
||||||
|
myHeaders.append("Content-Type", "application/json");
|
||||||
|
|
||||||
|
var raw = JSON.stringify(data);
|
||||||
|
|
||||||
|
console.log("Enviando disponibilidade para atualização (PATCH):", data);
|
||||||
|
|
||||||
|
var requestOptions = {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: myHeaders,
|
||||||
|
body: raw,
|
||||||
|
redirect: "follow",
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${ENDPOINT_AVAILABILITY}?id=eq.${availabilityId}`,
|
||||||
|
requestOptions
|
||||||
|
);
|
||||||
|
console.log("Resposta PATCH Disponibilidade:", response);
|
||||||
|
alert("Disponibilidade atualizada com sucesso!");
|
||||||
|
// Opcional: Redirecionar de volta para a lista de disponibilidades
|
||||||
|
// navigate('/disponibilidades');
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao atualizar disponibilidade:", error);
|
||||||
|
alert("Erro ao atualizar disponibilidade.");
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold mb-4">
|
||||||
|
{mode === "availability"
|
||||||
|
? `Editar Horário Disponível (ID: ${availabilityId.substring(0, 8)})`
|
||||||
|
: `Editar Médico (ID: ${DoctorID})`}
|
||||||
|
</h1>
|
||||||
|
|
||||||
<DoctorForm
|
<DoctorForm
|
||||||
onSave={HandlePutDoctor}
|
onSave={
|
||||||
|
mode === "availability" ? HandlePatchAvailability : HandlePutDoctor
|
||||||
formData={DoctorToPUT}
|
|
||||||
setFormData={setDoctorPUT}
|
|
||||||
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
formData={mode === "availability" ? availabilityToPATCH : DoctorToPUT}
|
||||||
|
setFormData={
|
||||||
|
mode === "availability" ? setAvailabilityToPATCH : setDoctorPUT
|
||||||
|
}
|
||||||
|
isEditingAvailability={mode === "availability"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default DoctorEditPage
|
export default DoctorEditPage;
|
||||||
|
|||||||
@ -104,7 +104,7 @@ function TableDoctor() {
|
|||||||
|
|
||||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions)
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(result => setMedicos(result))
|
.then(result => {setMedicos(result); console.log(result)})
|
||||||
.catch(error => console.log('error', error));
|
.catch(error => console.log('error', error));
|
||||||
}, [isAuthenticated, getAuthorizationHeader]);
|
}, [isAuthenticated, getAuthorizationHeader]);
|
||||||
|
|
||||||
|
|||||||
@ -9,12 +9,25 @@ function ForgotPassword() {
|
|||||||
setEmail(e.target.value);
|
setEmail(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (email) {
|
if (email) {
|
||||||
// Simulate sending email
|
try {
|
||||||
|
const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/auth/v1/otp", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ email })
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
setAlert("E-mail de verificação enviado!");
|
setAlert("E-mail de verificação enviado!");
|
||||||
// You can add your actual email logic here
|
console.log("Magic link enviado para:", email);
|
||||||
|
} else {
|
||||||
|
setAlert("Não foi possível enviar o e-mail. Tente novamente.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setAlert("Erro ao enviar e-mail. Tente novamente.");
|
||||||
|
console.error("Falha ao enviar magic link:", error);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setAlert("Preencha o campo de e-mail!");
|
setAlert("Preencha o campo de e-mail!");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import React, { useState, useEffect, use } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { useAuth } from "../components/utils/AuthProvider";
|
import { useAuth } from "../components/utils/AuthProvider";
|
||||||
import API_KEY from "../components/utils/apiKeys";
|
import API_KEY from "../components/utils/apiKeys";
|
||||||
import { UserInfos } from "../components/utils/Functions-Endpoints/General";
|
import { UserInfos } from "../components/utils/Functions-Endpoints/General";
|
||||||
|
import CabecalhoError from "../components/utils/fetchErros/CabecalhoError";
|
||||||
|
|
||||||
function Login({ onEnterSystem }) {
|
function Login({ onEnterSystem }) {
|
||||||
const { setAuthTokens } = useAuth();
|
const { setAuthTokens } = useAuth();
|
||||||
|
const [showCabecalho, setShowCabecalho ] = useState(false)
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
username: "",
|
username: "",
|
||||||
@ -126,6 +128,9 @@ function Login({ onEnterSystem }) {
|
|||||||
} else if (UserData?.roles?.includes("financeiro")) {
|
} else if (UserData?.roles?.includes("financeiro")) {
|
||||||
navigate(`/financeiro/`);
|
navigate(`/financeiro/`);
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
console.log("ERROROROROROOR")
|
||||||
|
setShowCabecalho(true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setAlert("Preencha todos os campos!");
|
setAlert("Preencha todos os campos!");
|
||||||
@ -135,9 +140,9 @@ function Login({ onEnterSystem }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mt-3 card-position">
|
<div className="mt-3 card-position">
|
||||||
<div className="col-lg-5 col-md-7 col-sm-9 col-12 mx-auto">
|
<div className="col-lg-5 col-md-7 col-sm-9 col-5 mx-auto">
|
||||||
<div className="card shadow-sm d-flex justify-content-between align-items-center">
|
<div className="card w-10 shadow-sm d-flex justify-content-between align-items-center">
|
||||||
<div id="auth-left" className="w-100">
|
<div id="auth-left" className="w-10">
|
||||||
<div className="auth-logo">
|
<div className="auth-logo">
|
||||||
<br />
|
<br />
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
@ -148,11 +153,7 @@ function Login({ onEnterSystem }) {
|
|||||||
<p className="auth-subtitle mb-5">
|
<p className="auth-subtitle mb-5">
|
||||||
Entre com os dados que você inseriu durante o registro.
|
Entre com os dados que você inseriu durante o registro.
|
||||||
</p>
|
</p>
|
||||||
{alert && (
|
<CabecalhoError showCabecalho={showCabecalho} message={"E-mail ou senha incorretos."}/>
|
||||||
<div className="alert alert-info" role="alert">
|
|
||||||
{alert}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<form onSubmit={handleLogin}>
|
<form onSubmit={handleLogin}>
|
||||||
<div className="form-group position-relative has-icon-left mb-4">
|
<div className="form-group position-relative has-icon-left mb-4">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -72,7 +72,7 @@ function PatientCadastroManager({ setCurrentPage }) {
|
|||||||
|
|
||||||
const cleanedData = {
|
const cleanedData = {
|
||||||
full_name: patientData.full_name,
|
full_name: patientData.full_name,
|
||||||
cpf: cpfLimpo,
|
cpf: patientData.cpf,
|
||||||
email: patientData.email,
|
email: patientData.email,
|
||||||
phone_mobile: patientData.phone_mobile,
|
phone_mobile: patientData.phone_mobile,
|
||||||
birth_date: patientData.birth_date || null,
|
birth_date: patientData.birth_date || null,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
function Register() {
|
function Register() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
@ -14,11 +15,12 @@ function Register() {
|
|||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
setForm({ ...form, [e.target.name]: e.target.value });
|
setForm({ ...form, [e.target.name]: e.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogin = (e) => {
|
const handleLogin = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (
|
if (
|
||||||
form.email &&
|
form.email &&
|
||||||
@ -31,18 +33,98 @@ function Register() {
|
|||||||
setAlert("As senhas não coincidem!");
|
setAlert("As senhas não coincidem!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// ...register logic...
|
try {
|
||||||
navigate('/secretaria/inicio');
|
const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/auth/v1/otp", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: form.email,
|
||||||
|
username: form.username,
|
||||||
|
userType: form.userType,
|
||||||
|
password: form.password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
setAlert("Cadastro realizado! Verifique seu e-mail.");
|
||||||
|
console.log("Usuário cadastrado:", form.email);
|
||||||
|
} else {
|
||||||
|
setAlert("Não foi possível cadastrar. Tente novamente.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setAlert("Erro ao cadastrar. Tente novamente.");
|
||||||
|
console.error("Falha ao cadastrar usuário:", error);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setAlert("Preencha todos os campos!");
|
setAlert("Preencha todos os campos!");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loginWithPassword = async (email, password) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://mock.apidog.com/m1/1053378-0-default/auth/v1/token?grant_type=password', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer <seu-token>',
|
||||||
|
'apikey': '<sua-api-key>',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: email,
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log("Login bem-sucedido:", data);
|
||||||
|
setAlert("Login realizado com sucesso!");
|
||||||
|
} else {
|
||||||
|
console.error("Falha no login:", data);
|
||||||
|
setAlert(data.message || "E-mail ou senha incorretos.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro na chamada de login:", error);
|
||||||
|
setAlert("Ocorreu um erro de rede ao tentar fazer login.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendMagicLink = async (email) => {
|
||||||
|
const magicLinkEndpoint = "https://mock.apidog.com/m1/1053378-0-default/auth/v1/otp";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(magicLinkEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer <seu-token>',
|
||||||
|
'apikey': '<sua-api-key>',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: email
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log("Magic link enviado:", data);
|
||||||
|
setAlert("Magic link enviado para o seu e-mail! Verifique sua caixa de entrada.");
|
||||||
|
} else {
|
||||||
|
console.error("Falha ao enviar magic link:", data);
|
||||||
|
setAlert(data.message || "Não foi possível enviar o magic link.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro na chamada de envio de magic link:", error);
|
||||||
|
setAlert("Ocorreu um erro de rede ao enviar o magic link.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mt-3 card-position">
|
<div className="mt-3 card-position">
|
||||||
<div className="col-lg-5 col-12">
|
<div className="col-lg-5 col-6">
|
||||||
<div className="card shadow-sm d-flex justify-content-between align-items-center">
|
<div className="card w-10 shadow-sm d-flex justify-content-between align-items-center">
|
||||||
<div id="auth-left">
|
<div id="auth-left">
|
||||||
<div className="auth-logo">
|
<div className="auth-logo">
|
||||||
<br />
|
<br />
|
||||||
@ -74,40 +156,8 @@ function Register() {
|
|||||||
<i className="bi bi-envelope" />
|
<i className="bi bi-envelope" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group position-relative has-icon-left mb-4">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="username"
|
|
||||||
className="form-control form-control-xl"
|
|
||||||
placeholder="Nome de usuário"
|
|
||||||
value={form.username}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<div className="form-control-icon">
|
|
||||||
<i className="bi bi-person" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="form-group position-relative has-icon-left mb-4">
|
|
||||||
<select
|
|
||||||
name="userType"
|
|
||||||
className="form-control form-control-xl"
|
|
||||||
value={form.userType}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="" disabled>
|
|
||||||
Selecione o tipo de usuário
|
|
||||||
</option>
|
|
||||||
<option value="paciente">Paciente</option>
|
|
||||||
<option value="secretaria">Secretaria</option>
|
|
||||||
<option value="medico">Médico</option>
|
|
||||||
<option value="admin">Admin</option>
|
|
||||||
</select>
|
|
||||||
<div className="form-control-icon">
|
|
||||||
<i className="bi bi-person" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="form-group position-relative has-icon-left mb-4">
|
<div className="form-group position-relative has-icon-left mb-4">
|
||||||
<input
|
<input
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
@ -179,4 +229,5 @@ function Register() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default Register;
|
export default Register;
|
||||||
@ -4,6 +4,8 @@ import API_KEY from "../components/utils/apiKeys";
|
|||||||
import { useAuth } from "../components/utils/AuthProvider";
|
import { useAuth } from "../components/utils/AuthProvider";
|
||||||
import "./style/TablePaciente.css";
|
import "./style/TablePaciente.css";
|
||||||
import ModalErro from "../components/utils/fetchErros/ModalErro";
|
import ModalErro from "../components/utils/fetchErros/ModalErro";
|
||||||
|
import manager from "../components/utils/fetchErros/ManagerFunction";
|
||||||
|
|
||||||
function TablePaciente({ setCurrentPage, setPatientID }) {
|
function TablePaciente({ setCurrentPage, setPatientID }) {
|
||||||
|
|
||||||
const { getAuthorizationHeader, isAuthenticated, RefreshingToken } = useAuth();
|
const { getAuthorizationHeader, isAuthenticated, RefreshingToken } = useAuth();
|
||||||
@ -24,7 +26,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const [selectedPatientId, setSelectedPatientId] = useState(null);
|
const [selectedPatientId, setSelectedPatientId] = useState(null);
|
||||||
|
|
||||||
const [showModalError, setShowModalError] = useState(false);
|
const [showModalError, setShowModalError] = useState("");
|
||||||
|
|
||||||
const [ ErrorInfo, setErrorInfo] = useState({})
|
const [ ErrorInfo, setErrorInfo] = useState({})
|
||||||
|
|
||||||
@ -117,7 +119,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
redirect: 'follow'
|
redirect: 'follow'
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions)
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patient", requestOptions)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
||||||
// 1. VERIFICAÇÃO DO STATUS HTTP (Se não for 2xx)
|
// 1. VERIFICAÇÃO DO STATUS HTTP (Se não for 2xx)
|
||||||
@ -142,21 +144,15 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
// 4. Bloco de SUCESSO
|
|
||||||
setPacientes(result);
|
setPacientes(result);
|
||||||
console.log("Sucesso:", result);
|
console.log("Sucesso:", result);
|
||||||
|
|
||||||
// IMPORTANTE: Se o modal estava aberto, feche-o no sucesso
|
|
||||||
setShowModalError(false);
|
setShowModalError(false);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// 5. Bloco de ERRO (Captura erros de rede ou o erro lançado pelo 'throw')
|
manager(setShowModalError, RefreshingToken, setErrorInfo, error)
|
||||||
//console.error('Falha na requisição:', error.message);
|
|
||||||
if(error.httpStatus === 401){
|
|
||||||
RefreshingToken()
|
|
||||||
}
|
|
||||||
setErrorInfo(error)
|
|
||||||
setShowModalError(true);
|
|
||||||
});
|
});
|
||||||
}, [isAuthenticated, getAuthorizationHeader]);
|
}, [isAuthenticated, getAuthorizationHeader]);
|
||||||
|
|
||||||
|
|||||||
@ -91,7 +91,7 @@
|
|||||||
background-color: #2c5e37;
|
background-color: #2c5e37;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legenda-item-confirmado{
|
.legenda-item-confirmed{
|
||||||
background-color: #1e90ff;
|
background-color: #1e90ff;
|
||||||
}
|
}
|
||||||
.legenda-item-cancelado{
|
.legenda-item-cancelado{
|
||||||
@ -102,7 +102,7 @@
|
|||||||
background-color: #f0ad4e;
|
background-color: #f0ad4e;
|
||||||
}
|
}
|
||||||
|
|
||||||
#status-card-consulta-realizado, .legenda-item-realizado {
|
#status-card-consulta-completed, .legenda-item-realizado {
|
||||||
background-color: #b7ffbd;
|
background-color: #b7ffbd;
|
||||||
border:3px solid #91d392;
|
border:3px solid #91d392;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@ -110,7 +110,7 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#status-card-consulta-cancelado, .legenda-item-cancelado {
|
#status-card-consulta-cancelled, .legenda-item-cancelado {
|
||||||
background-color: #ffb7cc;
|
background-color: #ffb7cc;
|
||||||
border:3px solid #ff6c84;
|
border:3px solid #ff6c84;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@ -118,7 +118,7 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#status-card-consulta-confirmado, .legenda-item-confirmed {
|
#status-card-consulta-confirmed, .legenda-item-confirmed {
|
||||||
background-color: #eef8fb;
|
background-color: #eef8fb;
|
||||||
border:3px solid #d8dfe7;
|
border:3px solid #d8dfe7;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@ -358,26 +358,18 @@ html[data-bs-theme="dark"] {
|
|||||||
#tabela-seletor-container i {
|
#tabela-seletor-container i {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
/* 1. Contêiner de Limitação de Largura e Posicionamento */
|
|
||||||
/* Este é o elemento mais importante. Ele deve envolver o input e o dropdown. */
|
|
||||||
.input-e-dropdown-wrapper {
|
.input-e-dropdown-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
/* IMPORTANTE: Defina aqui a largura EXATA que você deseja para o input
|
|
||||||
e para o dropdown. Na sua imagem, o input parece ter cerca de 300px ou mais.
|
|
||||||
*/
|
|
||||||
width: 350px; /* Ajuste este valor conforme a largura desejada do seu input */
|
|
||||||
|
|
||||||
/* Se o input original estava alinhado à direita (como na imagem),
|
width: 350px;
|
||||||
você pode precisar de um float ou margin para posicionar este wrapper.
|
margin-left: auto;
|
||||||
*/
|
|
||||||
margin-left: auto; /* Exemplo para alinhar o wrapper à direita se for o caso */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2. Estilização da Área de Busca (Input) */
|
|
||||||
/* Garante que o input utilize toda a largura do wrapper */
|
|
||||||
.busca-atendimento {
|
.busca-atendimento {
|
||||||
/* ... seus estilos de layout (flex, margin, etc.) para o busca-atendimento, se houver ... */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.busca-atendimento > div {
|
.busca-atendimento > div {
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import DoctorEditPage from "../../pages/DoctorEditPage";
|
|||||||
import UserDashboard from '../../PagesAdm/gestao.jsx';
|
import UserDashboard from '../../PagesAdm/gestao.jsx';
|
||||||
import PainelAdministrativo from '../../PagesAdm/painel.jsx';
|
import PainelAdministrativo from '../../PagesAdm/painel.jsx';
|
||||||
import admItems from "../../data/sidebar-items-adm.json";
|
import admItems from "../../data/sidebar-items-adm.json";
|
||||||
|
|
||||||
|
|
||||||
// ...restante do código...
|
// ...restante do código...
|
||||||
function Perfiladm() {
|
function Perfiladm() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import Sidebar from "../../components/Sidebar";
|
|||||||
import DoctorRelatorioManager from "../../PagesMedico/DoctorRelatorioManager";
|
import DoctorRelatorioManager from "../../PagesMedico/DoctorRelatorioManager";
|
||||||
import Prontuario from "../../PagesMedico/prontuario";
|
import Prontuario from "../../PagesMedico/prontuario";
|
||||||
import Relatorio from "../../PagesMedico/relatorio";
|
import Relatorio from "../../PagesMedico/relatorio";
|
||||||
import Agendamento from "../../PagesMedico/Agendamento";
|
import DoctorAgendamentoManager from "../../PagesMedico/DoctorAgendamentoManager";
|
||||||
import Chat from "../../PagesMedico/Chat";
|
import Chat from "../../PagesMedico/Chat";
|
||||||
import DoctorItems from "../../data/sidebar-items-medico.json";
|
import DoctorItems from "../../data/sidebar-items-medico.json";
|
||||||
import FormNovoRelatorio from "../../PagesMedico/FormNovoRelatorio";
|
import FormNovoRelatorio from "../../PagesMedico/FormNovoRelatorio";
|
||||||
@ -23,7 +23,7 @@ function PerfilMedico() {
|
|||||||
<Route path="/relatorios/:id/edit" element={<EditPageRelatorio />} />
|
<Route path="/relatorios/:id/edit" element={<EditPageRelatorio />} />
|
||||||
<Route path="/prontuario" element={<Prontuario />} />
|
<Route path="/prontuario" element={<Prontuario />} />
|
||||||
<Route path="/relatorios" element={<DoctorRelatorioManager />} />
|
<Route path="/relatorios" element={<DoctorRelatorioManager />} />
|
||||||
<Route path="/agendamentoMedico" element={<Agendamento />} />
|
<Route path="/agendamentoMedico" element={<DoctorAgendamentoManager />} />
|
||||||
<Route path="/chat" element={<Chat />} />
|
<Route path="/chat" element={<Chat />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,17 +1,27 @@
|
|||||||
import { Routes, Route } from "react-router-dom";
|
import { Routes, Route } from "react-router-dom";
|
||||||
import Sidebar from "../../components/Sidebar";
|
import Sidebar from "../../components/Sidebar";
|
||||||
import PacienteItems from "../../data/sidebar-items-paciente.json";
|
import PacienteItems from "../../data/sidebar-items-paciente.json";
|
||||||
import Agendamento from "../../pages/Agendamento";
|
import { useState } from "react";
|
||||||
import LaudoManager from "../../pages/LaudoManager";
|
import LaudoManager from "../../pages/LaudoManager";
|
||||||
|
import ConsultaCadastroManager from "../../PagesPaciente/ConsultaCadastroManager";
|
||||||
|
import ConsultasPaciente from "../../PagesPaciente/ConsultasPaciente";
|
||||||
|
import ConsultaEditPage from "../../PagesPaciente/ConsultaEditPage";
|
||||||
function PerfilPaciente({ onLogout }) {
|
function PerfilPaciente({ onLogout }) {
|
||||||
|
|
||||||
|
const [dadosConsulta, setConsulta] = useState({})
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<div id="app" className="active">
|
<div id="app" className="active">
|
||||||
<Sidebar onLogout={onLogout} menuItems={PacienteItems} />
|
<Sidebar onLogout={onLogout} menuItems={PacienteItems} />
|
||||||
|
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<LaudoManager />} />
|
<Route path="/" element={<LaudoManager />} />
|
||||||
<Route path="agendamento" element={<Agendamento />} />
|
<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="laudo" element={<LaudoManager />} />
|
||||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
//import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
//import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
import { Routes, Route } from "react-router-dom";
|
import { Routes, Route } from "react-router-dom";
|
||||||
|
import { useState } from "react";
|
||||||
import Sidebar from "../../components/Sidebar";
|
import Sidebar from "../../components/Sidebar";
|
||||||
import FinanceiroDashboard from "../../pages/FinanceiroDashboard";
|
import HorariosDisponibilidade from "../../components/doctors/HorariosDisponibilidade";
|
||||||
import SecretariaItems from "../../data/sidebar-items-secretaria.json";
|
import SecretariaItems from "../../data/sidebar-items-secretaria.json";
|
||||||
import Inicio from "../../pages/Inicio";
|
import Inicio from "../../pages/Inicio";
|
||||||
import TablePaciente from "../../pages/TablePaciente";
|
import TablePaciente from "../../pages/TablePaciente";
|
||||||
@ -16,9 +16,11 @@ import EditPage from "../../pages/EditPage";
|
|||||||
import DoctorDetails from "../../pages/DoctorDetails";
|
import DoctorDetails from "../../pages/DoctorDetails";
|
||||||
import DoctorEditPage from "../../pages/DoctorEditPage";
|
import DoctorEditPage from "../../pages/DoctorEditPage";
|
||||||
import ExcecoesDisponibilidade from "../../pages/ExcecoesDisponibilidade";
|
import ExcecoesDisponibilidade from "../../pages/ExcecoesDisponibilidade";
|
||||||
|
import DisponibilidadesDoctorPage from "../../pages/DisponibilidadesDoctorPage"
|
||||||
import AgendamentoEditPage from "../../pages/AgendamentoEditPage";
|
import AgendamentoEditPage from "../../pages/AgendamentoEditPage";
|
||||||
|
|
||||||
function PerfilSecretaria({ onLogout }) {
|
function PerfilSecretaria({ onLogout }) {
|
||||||
|
const [DictInfo, setDictInfo] = useState({})
|
||||||
return (
|
return (
|
||||||
// <Router>
|
// <Router>
|
||||||
<div id="app" className="active">
|
<div id="app" className="active">
|
||||||
@ -34,11 +36,13 @@ function PerfilSecretaria({ onLogout }) {
|
|||||||
<Route path="pacientes/:id/edit" element={<EditPage />} />
|
<Route path="pacientes/:id/edit" element={<EditPage />} />
|
||||||
<Route path="medicos/:id" element={<DoctorDetails />} />
|
<Route path="medicos/:id" element={<DoctorDetails />} />
|
||||||
<Route path="medicos/:id/edit" element={<DoctorEditPage />} />
|
<Route path="medicos/:id/edit" element={<DoctorEditPage />} />
|
||||||
<Route path="agendamento" element={<Agendamento />} />
|
<Route path="agendamento" element={<Agendamento setDictInfo={setDictInfo}/>} />
|
||||||
<Route path="agendamento/:id/edit" element={<AgendamentoEditPage/>} />
|
<Route path="agendamento/:id/edit" element={<AgendamentoEditPage setDictInfo={setDictInfo} DictInfo={DictInfo}/>} />
|
||||||
<Route path="laudo" element={<LaudoManager />} />
|
<Route path="laudo" element={<LaudoManager />} />
|
||||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
<Route path="disponibilidade" element={<DisponibilidadesDoctorPage />} />
|
||||||
|
<Route path="horarios" element={<HorariosDisponibilidade/>}/>
|
||||||
<Route path="excecoes-disponibilidade" element={<ExcecoesDisponibilidade />} />
|
<Route path="excecoes-disponibilidade" element={<ExcecoesDisponibilidade />} />
|
||||||
|
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user