Compare commits

...

23 Commits

Author SHA1 Message Date
joao_pedro
a8fc3eb397 Criacao manager e erro login 2025-10-25 17:04:39 -03:00
joao_pedro
95054bb9c1 Erro de login 2025-10-25 17:01:39 -03:00
joao_pedro
844ebf03a8 Mudanca para registrar paciente 2025-10-25 11:39:11 -03:00
joao_pedro
b0306125ae Melhorias no agendamento 2025-10-24 11:28:59 -03:00
38ebb5f7f3 tiptap3 2025-10-23 14:21:10 -03:00
e4386c1776 tiptap3 2025-10-23 14:18:36 -03:00
d80b13e404 Merge branch 'Disponibilidade3' 2025-10-23 13:06:12 -03:00
272be628aa Corrige API de disponibilidade e tratamento de erros 2025-10-23 12:58:13 -03:00
joao_pedro
6d18a23dd7 Melhoria editar 2025-10-23 11:47:49 -03:00
joao_pedro
1ba45e6c67 Botoes editar e apagar 2025-10-23 11:25:10 -03:00
joao_pedro
cf3ea901b8 Alteracoes nos agendamenos 2025-10-23 10:11:55 -03:00
joao_pedro
aa399d2d99 merge com melhoriasAgendamentos 2025-10-23 06:40:51 -03:00
joao_pedro
191e997180 Merge com fetchErrosAPI 2025-10-23 06:39:38 -03:00
joao_pedro
503a03e0b4 Melhoria botoes e agendamento para paciente 2025-10-23 06:16:43 -03:00
ffa909734f endpoints de hugo 2025-10-22 23:20:49 -03:00
ba98884667 Agendamento 2025-10-21 14:33:17 -03:00
d649d0ebc1 Merge branch 'Disponibilidades2' 2025-10-21 13:53:26 -03:00
ce3c5b8f22 Reorganização das Disponibilidades 2025-10-21 09:59:24 -03:00
joao_pedro
c87bfbb4de Criacao da pagnia de agendamento para o medico 2025-10-20 11:36:02 -03:00
joao_pedro
96505d081a Pequenas melhorias 2025-10-20 10:34:29 -03:00
joao_pedro
8f7fef3b8d Merge com melhorias agendamentos 2025-10-20 09:39:00 -03:00
jp-lima
edbb01e004 Funcionalidade de editar e apagar e melhoria estilo 2025-10-18 16:03:28 -03:00
jp-lima
b07534bdc4 Melhorias no agendamento 2025-10-17 19:38:26 -03:00
48 changed files with 4530 additions and 1568 deletions

768
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,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;

View 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;

View File

@ -1,130 +1,131 @@
// 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
useEffect(() => {
const fetchReports = async () => {
try {
var myHeaders = new Headers();
myHeaders.append('apikey', API_KEY);
myHeaders.append('Authorization', authHeader);
var requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
const res = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*", requestOptions);
const data = await res.json();
setRelatorios(data || []);
} catch (err) {
console.error('Erro listar relatórios', err);
setRelatorios([]);
}
};
fetchReports();
}, [authHeader]);
// depois que RelatoriosFiltrados mudar, busca pacientes e médicos correspondentes
useEffect(() => {
const fetchRelData = async () => {
const pacientes = [];
const medicos = [];
for (let i = 0; i < RelatoriosFiltrados.length; i++) { for (let i = 0; i < RelatoriosFiltrados.length; i++) {
let relatorio = RelatoriosFiltrados[i]; const rel = RelatoriosFiltrados[i];
let paciente_id = relatorio.patient_id; // paciente
const paciente = await GetPatientByID(paciente_id, authHeader); try {
console.log(paciente) const pacienteRes = await GetPatientByID(rel.patient_id, authHeader);
if (paciente.length > 0) { pacientes.push(Array.isArray(pacienteRes) ? pacienteRes[0] : pacienteRes);
pacientesDosRelatorios.push(paciente[0]); } 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(pacientesDosRelatorios); setPacientesComRelatorios(pacientes);
setMedicosComRelatorios(medicos);
};
if (RelatoriosFiltrados.length > 0) fetchRelData();
else {
setPacientesComRelatorios([]);
setMedicosComRelatorios([]);
} }
ListarPacientes()
}, [RelatoriosFiltrados, authHeader]); }, [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 const BaixarPDFdoRelatorio = (nome_paciente) => {
useEffect(() => { const elemento = document.getElementById("folhaA4");
var myHeaders = new Headers(); const opt = { margin: 0, filename: `relatorio_${nome_paciente || "paciente"}.pdf`, html2canvas: { scale: 2 }, jsPDF: { unit: "mm", format: "a4", orientation: "portrait" } };
myHeaders.append("apikey", API_KEY); html2pdf().set(opt).from(elemento).save();
myHeaders.append("Authorization", authHeader);
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&status", requestOptions)
.then(response => response.json())
.then(data => { setRelatorios(data); console.log(data) })
.catch(error => console.log('error', error));
}, [authHeader])
const BaixarPDFdoRelatorio = (nome_paciente) => {
const elemento = document.getElementById("folhaA4"); // tua div do relatório
const opt = {
margin: 0,
filename: `relatorio_${nome_paciente || "paciente"}.pdf`,
html2canvas: { scale: 2 },
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
}; };
html2pdf().set(opt).from(elemento).save();
} return (
return (
<div> <div>
{showModal && ( {showModal && (
<div className="modal" > <div className="modal">
<div className="modal-dialog modal-tabela-relatorio"> <div className="modal-dialog modal-tabela-relatorio">
<div className="modal-content"> <div className="modal-content">
<div className="modal-header text-white"> <div className="modal-header text-white">
<h5 className="modal-title ">Relatório de {PacientesComRelatorios[index]?.full_name} </h5> <h5 className="modal-title ">Relatório de {PacientesComRelatorios[index]?.full_name}</h5>
<button <button type="button" className="btn-close" onClick={() => setShowModal(false)}></button>
type="button" </div>
className="btn-close" <div className="modal-body">
onClick={() => setShowModal(false)} <div id="folhaA4">
></button> <div id='header-relatorio'>
</div> <p>Clinica Rise up</p>
<div className="modal-body"> <p>Dr - CRM/SP 123456</p>
<div id="folhaA4"> <p>Avenida - (79) 9 4444-4444</p>
<div id='header-relatorio'> </div>
<p>Clinica Rise up</p>
<p>Dr - CRM/SP 123456</p> <div id='infoPaciente'>
<p>Avenida - (79) 9 4444-4444</p> <p>Paciente: {PacientesComRelatorios[index]?.full_name}</p>
</div> <p>Data de nascimento: {PacientesComRelatorios[index]?.birth_date}</p>
<div id='infoPaciente'> <p>Data do exame: {RelatoriosFiltrados[index]?.due_at || ''}</p>
<p>Paciente: {PacientesComRelatorios[index]?.full_name}</p> {/* Exibe conteúdo salvo (content_html) */}
<p>Data de nascimento: {PacientesComRelatorios[index]?.birth_date} </p> <p style={{ marginTop: '15px', fontWeight: 'bold' }}>Conteúdo do Relatório:</p>
<p>Data do exame: {}</p> <TiptapViewer htmlContent={RelatoriosFiltrados[index]?.content_html || RelatoriosFiltrados[index]?.content || 'Relatório não preenchido.'} />
<p>Exame: {RelatoriosFiltrados[index]?.exam}</p> </div>
{/* INÍCIO DA MUDANÇA (da resposta anterior) */}
<p style={{ marginTop: '15px', fontWeight: 'bold' }}>Conteúdo do Relatório:</p> <div>
<TiptapViewer <p>Dr {MedicosComRelatorios[index]?.full_name || RelatoriosFiltrados[index]?.requested_by}</p>
htmlContent={ <p>Emitido em: {RelatoriosFiltrados[index]?.created_at || '—'}</p>
RelatoriosFiltrados[index]?.content ||
RelatoriosFiltrados[index]?.diagnosis ||
RelatoriosFiltrados[index]?.conclusion ||
'Relatório não preenchido.'
}
/>
{/* FIM DA MUDANÇA */}
</div>
<div>
<p>Dr {RelatoriosFiltrados[index]?.required_by}</p>
<p>Emitido em: 0</p>
</div>
</div> </div>
</div> </div>
<div className="modal-footer"> </div>
<button className="btn btn-primary" onClick={() => BaixarPDFdoRelatorio(PacientesComRelatorios[index]?.full_name)}><i className='bi bi-file-pdf-fill'></i> baixar em pdf</button> <div className="modal-footer">
<button <button className="btn btn-primary" onClick={() => BaixarPDFdoRelatorio(PacientesComRelatorios[index]?.full_name)}><i className='bi bi-file-pdf-fill'></i> baixar em pdf</button>
type="button" <button type="button" className="btn btn-primary" onClick={() => { setShowModal(false) }}>Fechar</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); }}>
<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`)}>
className="btn btn-sm" <i className="bi bi-pencil me-1"></i> Editar
style={{ </button>
backgroundColor: "#E6F2FF", </div>
color: "#004085",
}}
onClick={() => {
setShowModal(true); setIndex(index)
}}
>
<i className="bi bi-eye me-1"></i> Ver Detalhes
</button>
<button
className="btn btn-sm"
style={{
backgroundColor: "#FFF3CD",
color: "#856404",
}}
onClick={() => {
// MANTIDO: Uso de string template para a navegação
navigate(`/medico/relatorios/${relatorio.id}/edit`)
}}
>
<i className="bi bi-pencil me-1"></i> Editar
</button>
</div>
</td> </td>
</tr> </tr>
)) ))
) : ( ) : (
<tr> <tr><td colSpan="8" className="text-center">Nenhum paciente encontrado.</td></tr>
<td colSpan="8" className="text-center">
Nenhum paciente encontrado.
</td>
</tr>
)} )}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
</div> </div>
</div> </div>
) );
} };
export default DoctorRelatorioManager
export default DoctorRelatorioManager;

View File

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

View File

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

View File

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

View File

@ -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

View 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

View File

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

View File

@ -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

View 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
View 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 */
}

View File

@ -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>

View File

@ -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})
} }
} }
const handleSubmit = (e) => {
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) => {
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 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>
<div className="campos-informacoes-paciente" id="informacoes-paciente-linha-tres"> <div className="campos-informacoes-paciente" id="informacoes-paciente-linha-tres">
<div className="campo-de-input"> <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"> <div className="campo-de-input-container"> {/* NOVO CONTAINER PAI */}
<label>Nome do profissional *</label> <div className="campo-de-input">
<input type="text" name="profissional" onChange={handleChange} value={agendamento.nome_medico}required /> <label>Nome do profissional *</label>
</div> <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>
{/* 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">
{/* Dropdown de Início (Não modificado) */}
<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 className="campo-informacoes-atendimento"> {/* SELETOR DE SESSÕES MODIFICADO */}
<div className="campo-de-input"> {/* Removemos o 'label' para evitar o desalinhamento e colocamos o texto acima */}
<label>Início *</label> <div className='seletor-wrapper'>
<input type="time" name="inicio" required /> <label>Número de Sessões *</label> {/* Novo label para o seletor */}
</div> <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>
<div className="campo-de-input"> <p className='sessao-valor'>{sessoes}</p> {/* Adicionada classe para estilização */}
<label>Término *</label>
<input type="time" name="termino" required /> <button
</div> 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 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>
<div className="campo-de-input">
<label>Profissional solicitante</label>
<select name="solicitante">
<option value="" disabled invisible selected>Selecione o solicitante</option>
<option value="secretaria">Secretária</option>
<option value="medico">Médico</option>
</select>
</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>
); );

View File

@ -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>

View File

@ -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(() => {
@ -183,13 +181,13 @@ const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos }) => {
</div> </div>
</div> </div>
<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>

View File

@ -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>

View 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; */
}

View File

@ -110,6 +110,7 @@ svg{
margin-top: 25px; margin-top: 25px;
display: flex; display: flex;
gap: 12px; gap: 12px;
justify-content: flex-end;
} }
.btn-primary { .btn-primary {
@ -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) */
}

View File

@ -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 */

View File

@ -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 */
} }

View File

@ -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 });
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) { } catch (error) {
throw 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
@ -624,29 +705,33 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
</div> </div>
{/* HORÁRIOS */} {/* HORÁRIOS */}
<div className="form-section"> <div className="form-section">
<h4 <h4
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" : ""}`}
<HorariosDisponibilidade >
onChange={(dados) => { <div className="row mt-3">
console.log("Disponibilidades atualizadas:", dados); <div className="col-12 mb-3">
// Se quiser salvar no formData: <p className="form-label text-muted">
// setFormData(prev => ({ ...prev, disponibilidades: dados })); Defina seus horários de atendimento para cada dia da semana.
}} Marque um dia para começar a adicionar blocos de tempo.
/> </p>
</div> <HorariosDisponibilidade
</div> initialAvailability={formData.availability}
onUpdate={handleAvailabilityUpdate}
/>
</div> </div>
</div>
</div>
</div>
{/* BOTÕES DE AÇÃO */} {/* BOTÕES DE AÇÃO */}
<div className="actions-container"> <div className="actions-container">
@ -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>
</> </>
); );
} }

View File

@ -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,
}))
);
function handleToggleDia(index) { useEffect(() => {
const updated = [...disponibilidades]; if (initialAvailability !== emptyAvailabilityTemplate) {
updated[index].ativo = !updated[index].ativo; setAvailability(initialAvailability);
setDisponibilidades(updated); }
onChange?.(updated); }, [initialAvailability]);
}
function handleSlotChange(indexDia, indexSlot, campo, valor) { useEffect(() => {
const updated = [...disponibilidades]; if (onUpdate) {
updated[indexDia].slots[indexSlot][campo] = valor; onUpdate(availability);
setDisponibilidades(updated); }
onChange?.(updated); }, [availability, onUpdate]);
}
function handleAddSlot(indexDia) { const handleDayCheck = useCallback((dayIndex, currentIsChecked) => {
const updated = [...disponibilidades]; const isChecked = !currentIsChecked;
updated[indexDia].slots.push({ start_time: "", end_time: "" });
setDisponibilidades(updated);
onChange?.(updated);
}
function handleRemoveSlot(indexDia, indexSlot) { setAvailability((prev) =>
const updated = [...disponibilidades]; prev.map((day, i) =>
updated[indexDia].slots.splice(indexSlot, 1); i === dayIndex
setDisponibilidades(updated); ? {
onChange?.(updated); ...day,
} isChecked,
blocos: isChecked
? day.blocos.length === 0
? [
{
...initialBlockTemplate,
id: Date.now() + Math.random(),
isNew: true,
},
]
: day.blocos
: [],
}
: day
)
);
}, []);
return ( const handleAddBlock = useCallback((dayIndex) => {
<div className="space-y-4"> const tempId = Date.now() + Math.random();
{disponibilidades.map((dia, i) => ( const newBlock = { ...initialBlockTemplate, id: tempId, isNew: true };
<div key={dia.weekday} className="border p-3 rounded-md bg-gray-50">
<div className="flex items-center justify-between"> setAvailability((prev) =>
<label className="font-semibold capitalize"> prev.map((day, i) =>
{dia.weekday} i === dayIndex
</label> ? {
...day,
blocos: [...day.blocos, newBlock],
isChecked: true,
}
: day
)
);
}, []);
const handleRemoveBlock = useCallback((dayIndex, blockId) => {
setAvailability((prev) =>
prev.map((day, i) => {
if (i === dayIndex) {
const newBlocos = day.blocos.filter((bloco) => bloco.id !== blockId);
return {
...day,
blocos: newBlocos,
isChecked: newBlocos.length > 0,
};
}
return day;
})
);
}, []);
const handleTimeChange = useCallback((dayIndex, blockId, field, value) => {
setAvailability((prev) =>
prev.map((day, i) =>
i === dayIndex
? {
...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 <input
type="checkbox" id={`inicio-${dayIndex}-${bloco.id}`}
checked={dia.ativo} type="time"
onChange={() => handleToggleDia(i)} 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>
{dia.ativo && (
<div className="mt-2 space-y-2">
{dia.slots.map((slot, j) => (
<div key={j} className="flex gap-2 items-center">
<input
type="time"
value={slot.start_time}
onChange={e => handleSlotChange(i, j, "start_time", e.target.value)}
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
</button>
)}
</div>
))}
<button
type="button"
onClick={() => handleAddSlot(i)}
className="text-blue-600 hover:underline mt-2"
>
+ Adicionar pausa/bloco
</button>
</div>
)}
</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> </div>
); );
}
return (
<div
style={{
maxWidth: "960px",
margin: "0 auto",
fontFamily: "Inter, sans-serif",
}}
>
<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
type="checkbox"
checked={isChecked}
onChange={() => {}}
style={{
width: "20px",
height: "20px",
accentColor: isChecked ? "#3b82f6" : "#9ca3af",
marginLeft: "8px",
}}
/>
</label>
</div>
{isChecked && (
<div style={{ marginTop: "16px" }}>
{day.blocos.length === 0 && (
<p
style={{
color: "#6b7280",
fontStyle: "italic",
marginBottom: "16px",
}}
>
Nenhum bloco de horário definido.
</p>
)}
<div
style={{
display: "flex",
flexDirection: "column",
gap: "16px",
}}
>
{day.blocos.map((bloco) =>
renderTimeBlock(dayIndex, bloco)
)}
</div>
<button
onClick={() => handleAddBlock(dayIndex)}
style={{
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 novo bloco
</button>
</div>
)}
</div>
);
})}
</div>
</div>
);
};
export default HorariosDisponibilidade;

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ return(
<div> <div>
{showModal ? {showModal === "modal"?
<div className="modal-overlay"> <div className="modal-overlay">

View File

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

View File

@ -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"
} }
] ]

View File

@ -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,16 +21,19 @@ import { Search } from 'lucide-react';
const Agendamento = () => { const Agendamento = ({setDictInfo}) => {
const navigate = useNavigate(); const navigate = useNavigate();
const [FiladeEspera, setFiladeEspera] = useState(false);
const [selectedID, setSelectedId] = useState('0')
const [filaEsperaData, setfilaEsperaData] = useState([])
const [FiladeEspera, setFiladeEspera] = useState(false);
const [tabela, setTabela] = useState('diario'); const [tabela, setTabela] = useState('diario');
const [PageNovaConsulta, setPageConsulta] = useState(false); const [PageNovaConsulta, setPageConsulta] = useState(false);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [agendamentos, setAgendamentos] = useState() const [agendamentos, setAgendamentos] = useState()
const {getAuthorizationHeader} = useAuth() const {getAuthorizationHeader} = useAuth()
const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({}) const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({})
const [showDeleteModal, setShowDeleteModal] = useState(false) const [showDeleteModal, setShowDeleteModal] = useState(false)
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState() const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState()
@ -42,32 +45,71 @@ 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) => {
const DiaAgendamento = agendamento.scheduled_at.split("T")[0];
//console.log(DictAgendamentosOrganizados) let medico = await GetDoctorByID(doctor_id, authHeader);
let paciente = await GetPatientByID(patient_id, authHeader);
if (DiaAgendamento in DictAgendamentosOrganizados) { console.log(medico)
// já existe a data adiciona na lista
DictAgendamentosOrganizados[DiaAgendamento].push(agendamento); let dicionario = {
} else { agendamento: agendamento,
// não existe cria nova key com uma lista Infos: {
DictAgendamentosOrganizados[DiaAgendamento] = [agendamento]; 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];
if (DiaAgendamento in DictAgendamentosOrganizados) {
DictAgendamentosOrganizados[DiaAgendamento].push(agendamento);
} else {
DictAgendamentosOrganizados[DiaAgendamento] = [agendamento];
}
}
}
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;
});
} }
}
setAgendamentosOrganizados(DictAgendamentosOrganizados); 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) {
// Requisição inicial para mostrar os agendamentos do banco de dados 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);
myHeaders.append("apikey", API_KEY) myHeaders.append("apikey", API_KEY)
@ -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,61 +258,64 @@ 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')}>
<i className="bi bi-gear-fill me-1"></i> <div className="btns-gerenciamento-e-consulta" style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
Mudar Disponibilidade <button className="btn btn-primary" onClick={() => setPageConsulta(true)}>
</button> <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>
Mudar Disponibilidade
</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>
<div className='busca-atendimento'> <i className="fa-solid fa-calendar-day"></i>
<div> <input
<i className="fa-solid fa-calendar-day"></i> type="text"
<input placeholder="Filtrar atendimento por médico..."
type="text" value={searchTermDoctor}
placeholder="Filtrar atendimento por médico..." onChange={(e) => handleSearchMedicos(e.target.value)}
value={searchTermDoctor} />
onChange={(e) => handleSearchMedicos(e.target.value)} // Chama a nova função de filtro </div>
/>
</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={() => {
setSearchTermDoctor(medico.nomeMedico);
}}
>
<p>{medico.nomeMedico} </p>
</div>
))}
</div>
)}
</div>
</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>
<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>

View File

@ -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>
) )

View File

@ -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>

View 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;

View File

@ -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);

View File

@ -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");
GetDoctorByID(DoctorID, authHeader) fetch(`${ENDPOINT_AVAILABILITY}?id=eq.${availabilityId}&select=*`, {
.then((data) => { method: "GET",
console.log(data, "médico vindo da API"); headers: {
setDoctorPUT(data[0]) apikey: API_KEY,
; // supabase retorna array Authorization: authHeader,
},
}) })
.catch((err) => console.error("Erro ao buscar paciente:", err)); .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)
.then((data) => {
console.log(data, "médico vindo da API");
setDoctorPUT(data[0]);
})
.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();
myHeaders.append("apikey", API_KEY);
myHeaders.append("Authorization", authHeader);
myHeaders.append("Content-Type", "application/json");
var myHeaders = new Headers(); var raw = JSON.stringify(DoctorToPUT);
myHeaders.append('apikey', API_KEY)
myHeaders.append("Authorization", authHeader);
myHeaders.append("Content-Type", "application/json");
var raw = JSON.stringify(DoctorToPUT); console.log("Enviando médico para atualização (PUT):", DoctorToPUT);
console.log("Enviando médico para atualização:", DoctorToPUT); var requestOptions = {
method: "PUT",
headers: myHeaders,
body: raw,
redirect: "follow",
};
var requestOptions = { try {
method: 'PUT', const response = await fetch(
headers: myHeaders, `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`,
body: raw, requestOptions
redirect: 'follow' );
console.log("Resposta PUT Doutor:", response);
alert("Dados do médico atualizados com sucesso!");
} catch (error) {
console.error("Erro ao atualizar médico:", error);
alert("Erro ao atualizar dados do médico.");
throw error;
}
}; };
try { // 2. Função para Atualizar DISPONIBILIDADE (PATCH)
const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`,requestOptions); const HandlePatchAvailability = async (data) => {
console.log(response) const authHeader = getAuthorizationHeader();
} catch (error) { var myHeaders = new Headers();
console.error("Erro ao atualizar paciente:", error); myHeaders.append("apikey", API_KEY);
throw error; 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} formData={mode === "availability" ? availabilityToPATCH : DoctorToPUT}
setFormData={
/> mode === "availability" ? setAvailabilityToPATCH : setDoctorPUT
}
isEditingAvailability={mode === "availability"}
/>
</div> </div>
) );
} };
export default DoctorEditPage export default DoctorEditPage;

View File

@ -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]);

View File

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

View File

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

View File

@ -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,

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
function Register() { function Register() {
const navigate = useNavigate(); const navigate = useNavigate();
const [form, setForm] = useState({ const [form, setForm] = useState({
@ -14,11 +15,12 @@ function Register() {
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const handleChange = (e) => { const handleChange = (e) => {
setForm({ ...form, [e.target.name]: e.target.value }); setForm({ ...form, [e.target.name]: e.target.value });
}; };
const handleLogin = (e) => { const handleLogin = async (e) => {
e.preventDefault(); e.preventDefault();
if ( if (
form.email && form.email &&
@ -31,152 +33,201 @@ 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 />
<Link to="/"> <Link to="/">
<h1 className="mb-4 text-center">MediConnect</h1> <h1 className="mb-4 text-center">MediConnect</h1>
</Link> </Link>
</div>
<h3 className="auth-title">Cadastre-se</h3>
<p className="auth-subtitle mb-5">
Insira seus dados para se registrar em nosso site.
</p>
{alert && (
<div className="alert alert-info" role="alert">
{alert}
</div> </div>
)} <h3 className="auth-title">Cadastre-se</h3>
<form> <p className="auth-subtitle mb-5">
<div className="form-group position-relative has-icon-left mb-4"> Insira seus dados para se registrar em nosso site.
<input
type="text"
name="email"
className="form-control form-control-xl"
placeholder="E-mail"
value={form.email}
onChange={handleChange}
required
/>
<div className="form-control-icon">
<i className="bi bi-envelope" />
</div>
</div>
<div className="form-group position-relative has-icon-left mb-4">
<input
type="text"
name="username"
className="form-control form-control-xl"
placeholder="Nome de usuário"
value={form.username}
onChange={handleChange}
required
/>
<div className="form-control-icon">
<i className="bi bi-person" />
</div>
</div>
<div className="form-group position-relative has-icon-left mb-4">
<select
name="userType"
className="form-control form-control-xl"
value={form.userType}
onChange={handleChange}
required
>
<option value="" disabled>
Selecione o tipo de usuário
</option>
<option value="paciente">Paciente</option>
<option value="secretaria">Secretaria</option>
<option value="medico">Médico</option>
<option value="admin">Admin</option>
</select>
<div className="form-control-icon">
<i className="bi bi-person" />
</div>
</div>
<div className="form-group position-relative has-icon-left mb-4">
<input
type={showPassword ? "text" : "password"}
name="password"
className="form-control form-control-xl"
placeholder="Senha"
value={form.password}
onChange={handleChange}
required
/>
<div className="form-control-icon">
<i className="bi bi-shield-lock" />
</div>
<button
type="button"
className="btn btn-sm"
style={{ position: "absolute", right: "10px", top: "10px", background: "none", border: "none" }}
onClick={() => setShowPassword(!showPassword)}
tabIndex={-1}
>
<i className={`bi ${showPassword ? "bi-eye-slash" : "bi-eye"}`}></i>
</button>
</div>
<div className="form-group position-relative has-icon-left mb-4">
<input
type={showConfirmPassword ? "text" : "password"}
name="confirmPassword"
className="form-control form-control-xl"
placeholder="Confirmar senha"
value={form.confirmPassword}
onChange={handleChange}
required
/>
<div className="form-control-icon">
<i className="bi bi-shield-lock" />
</div>
<button
type="button"
className="btn btn-sm"
style={{ position: "absolute", right: "10px", top: "10px", background: "none", border: "none" }}
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
tabIndex={-1}
>
<i className={`bi ${showConfirmPassword ? "bi-eye-slash" : "bi-eye"}`}></i>
</button>
</div>
<button className="btn btn-primary btn-block btn-lg shadow-lg mt-5"
onClick={handleLogin}>
Cadastrar
</button>
</form>
<div className="text-center mt-5 text-lg fs-4">
<p className="text-gray-600">
tem uma conta?
<Link className="font-bold" to={'/login'}>
Entrar
</Link>
.
</p> </p>
{alert && (
<div className="alert alert-info" role="alert">
{alert}
</div>
)}
<form>
<div className="form-group position-relative has-icon-left mb-4">
<input
type="text"
name="email"
className="form-control form-control-xl"
placeholder="E-mail"
value={form.email}
onChange={handleChange}
required
/>
<div className="form-control-icon">
<i className="bi bi-envelope" />
</div>
</div>
<div className="form-group position-relative has-icon-left mb-4">
<input
type={showPassword ? "text" : "password"}
name="password"
className="form-control form-control-xl"
placeholder="Senha"
value={form.password}
onChange={handleChange}
required
/>
<div className="form-control-icon">
<i className="bi bi-shield-lock" />
</div>
<button
type="button"
className="btn btn-sm"
style={{ position: "absolute", right: "10px", top: "10px", background: "none", border: "none" }}
onClick={() => setShowPassword(!showPassword)}
tabIndex={-1}
>
<i className={`bi ${showPassword ? "bi-eye-slash" : "bi-eye"}`}></i>
</button>
</div>
<div className="form-group position-relative has-icon-left mb-4">
<input
type={showConfirmPassword ? "text" : "password"}
name="confirmPassword"
className="form-control form-control-xl"
placeholder="Confirmar senha"
value={form.confirmPassword}
onChange={handleChange}
required
/>
<div className="form-control-icon">
<i className="bi bi-shield-lock" />
</div>
<button
type="button"
className="btn btn-sm"
style={{ position: "absolute", right: "10px", top: "10px", background: "none", border: "none" }}
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
tabIndex={-1}
>
<i className={`bi ${showConfirmPassword ? "bi-eye-slash" : "bi-eye"}`}></i>
</button>
</div>
<button className="btn btn-primary btn-block btn-lg shadow-lg mt-5"
onClick={handleLogin}>
Cadastrar
</button>
</form>
<div className="text-center mt-5 text-lg fs-4">
<p className="text-gray-600">
tem uma conta?
<Link className="font-bold" to={'/login'}>
Entrar
</Link>
.
</p>
</div>
</div>
<div className="col-lg-7 d-none d-lg-block">
<div id="auth-right"></div>
</div> </div>
</div>
<div className="col-lg-7 d-none d-lg-block">
<div id="auth-right"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </>
</>
); );
} }
export default Register; export default Register;

View File

@ -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]);

View File

@ -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 {

View File

@ -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 (

View File

@ -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>

View File

@ -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>

View File

@ -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>