Compare commits
12 Commits
edbb01e004
...
503a03e0b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
503a03e0b4 | ||
| ba98884667 | |||
| d649d0ebc1 | |||
| ce3c5b8f22 | |||
|
|
c87bfbb4de | ||
|
|
96505d081a | ||
|
|
8f7fef3b8d | ||
|
|
d2b18cd4dc | ||
| 4d661dd973 | |||
| fb5d3817ed | |||
|
|
0650ba5f10 | ||
|
|
b9e65065b7 |
524
package-lock.json
generated
524
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,226 +0,0 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
|
||||
import TabelaAgendamentoDia from '../components/AgendarConsulta/TabelaAgendamentoDia';
|
||||
import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana';
|
||||
import TabelaAgendamentoMes from '../components/AgendarConsulta/TabelaAgendamentoMes';
|
||||
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta';
|
||||
|
||||
// ✨ NOVO: Caminho de importação corrigido com base na sua estrutura de pastas
|
||||
import AgendamentosMes from '../components/AgendarConsulta/DadosConsultasMock.js';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
// Importe os estilos
|
||||
import "./styleMedico/Agendamento.css";
|
||||
import "./styleMedico/FilaEspera.css";
|
||||
|
||||
const Agendamento = () => {
|
||||
|
||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||
const [tabela, setTabela] = useState('diario');
|
||||
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
// Dados da fila de espera (sem alteração)
|
||||
const filaEsperaData = [
|
||||
{ nome: 'Ricardo Pereira', email: 'ricardo.pereira@gmail.com', cpf: '444.777.666-55', telefone: '(79) 99123-4567', entrada: '25/09/2025 às 08:00' },
|
||||
{ nome: 'Ana Costa', email: 'ana.costa@gmail.com', cpf: '321.654.987-00', telefone: '(79) 97777-3333', entrada: '25/09/2025 às 08:30' },
|
||||
{ nome: 'Lucas Martins', email: 'lucas.martins@gmail.com', cpf: '777.666.555-33', telefone: '(79) 99654-3210', entrada: '25/09/2025 às 09:00' },
|
||||
{ nome: 'João Souza', email: 'joao.souza@gmail.com', cpf: '987.654.321-00', telefone: '(79) 98888-2222', entrada: '25/09/2025 às 14:00' },
|
||||
{ nome: 'Maria Silva', email: 'maria.silva@gmail.com', cpf: '123.456.789-00', telefone: '(79) 99999-1111', entrada: '25/09/2025 às 14:30' },
|
||||
{ nome: 'Fernanda Lima', email: 'fernanda.lima@gmail.com', cpf: '888.999.000-22', telefone: '(79) 98877-6655', entrada: '26/09/2025 às 09:30' },
|
||||
{ nome: 'Carlos Andrade', email: 'carlos.andrade@gmail.com', cpf: '222.555.888-11', telefone: '(79) 99876-5432', entrada: '26/09/2025 às 10:00' },
|
||||
{ nome: 'Juliana Oliveira', email: 'juliana.o@gmail.com', cpf: '111.222.333-44', telefone: '(79) 98765-1234', entrada: '26/09/2025 às 11:30' },
|
||||
];
|
||||
|
||||
// Filtro da fila de espera (sem alteração)
|
||||
const filteredFila = filaEsperaData.filter(item =>
|
||||
item.nome.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
item.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
item.cpf.includes(searchTerm) ||
|
||||
item.telefone.includes(searchTerm)
|
||||
);
|
||||
|
||||
// Lógica para filtrar os dados da AGENDA (AgendamentosMes)
|
||||
const filteredAgendamentos = useMemo(() => {
|
||||
if (!searchTerm.trim()) {
|
||||
return AgendamentosMes;
|
||||
}
|
||||
|
||||
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
||||
const filteredData = {};
|
||||
|
||||
for (const semana in AgendamentosMes) {
|
||||
filteredData[semana] = {};
|
||||
for (const dia in AgendamentosMes[semana]) {
|
||||
filteredData[semana][dia] = AgendamentosMes[semana][dia].filter(agendamento =>
|
||||
agendamento.status === 'vazio' ||
|
||||
(agendamento.paciente && agendamento.paciente.toLowerCase().includes(lowerCaseSearchTerm))
|
||||
);
|
||||
}
|
||||
}
|
||||
return filteredData;
|
||||
}, [searchTerm]);
|
||||
|
||||
const ListarDiasdoMes = (ano, mes) => {
|
||||
let segundas = []; let tercas = []; let quartas = []; let quintas = []; let sextas = []
|
||||
const base = dayjs(`${ano}-${mes}-01`)
|
||||
const DiasnoMes = base.daysInMonth()
|
||||
for (let d = 1; d <= DiasnoMes; d++) {
|
||||
const data = dayjs(`${ano}-${mes}-${d}`)
|
||||
const dia = data.format('dddd')
|
||||
switch (dia) {
|
||||
case 'Monday': segundas.push(d); break
|
||||
case 'Tuesday': tercas.push(d); break
|
||||
case 'Wednesday': quartas.push(d); break
|
||||
case 'Thursday': quintas.push(d); break
|
||||
case 'Friday': sextas.push(d); break
|
||||
default: break
|
||||
}
|
||||
}
|
||||
let ListaDiasDatas = [segundas, tercas, quartas, quintas, sextas]
|
||||
return ListaDiasDatas
|
||||
}
|
||||
|
||||
const handleClickAgendamento = (agendamento) => {
|
||||
if (agendamento.status !== 'vazio') return
|
||||
else setPageConsulta(true)
|
||||
}
|
||||
|
||||
const handleClickCancel = () => setPageConsulta(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Agendar nova consulta</h1>
|
||||
|
||||
{!PageNovaConsulta ? (
|
||||
<div className='atendimento-eprocura'>
|
||||
<div className='busca-atendimento'>
|
||||
<div>
|
||||
<i className="fa-solid fa-calendar-day"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Buscar atendimento por paciente..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<select>
|
||||
<option value="" disabled selected>Agendar</option>
|
||||
<option value="">Atendimento</option>
|
||||
<option value="">Sessões</option>
|
||||
<option value="">Urgência</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='unidade-selecionarprofissional'>
|
||||
<select>
|
||||
<option value="" disabled selected >Unidade</option>
|
||||
<option value="">Unidade Central</option>
|
||||
<option value="">Unidade Zona Norte</option>
|
||||
<option value="">Unidade Zona Oeste</option>
|
||||
</select>
|
||||
<input type="text" placeholder='Selecionar profissional' />
|
||||
</div>
|
||||
|
||||
<div className='container-btns-agenda-fila_esepera'>
|
||||
<button
|
||||
className={`btn-agenda ${FiladeEspera === false ? "opc-agenda-ativo" : ""}`}
|
||||
onClick={() => {
|
||||
setFiladeEspera(false);
|
||||
setSearchTerm('');
|
||||
}}
|
||||
>
|
||||
Agenda
|
||||
</button>
|
||||
<button
|
||||
className={`btn-fila-espera ${FiladeEspera === true ? "opc-filaespera-ativo" : ""}`}
|
||||
onClick={() => {
|
||||
setFiladeEspera(true);
|
||||
setSearchTerm('');
|
||||
}}
|
||||
>
|
||||
Fila de espera
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section className='calendario-ou-filaespera'>
|
||||
{FiladeEspera === false ?
|
||||
(
|
||||
<div className='calendario'>
|
||||
<div>
|
||||
<section className='btns-e-legenda-container'>
|
||||
<div>
|
||||
<button className={`btn-selecionar-tabeladia ${tabela === "diario" ? "ativo" : ""}`} onClick={() => setTabela("diario")}>
|
||||
<i className="fa-solid fa-calendar-day"></i> Dia
|
||||
</button>
|
||||
<button className={`btn-selecionar-tabelasemana ${tabela === 'semanal' ? 'ativo' : ""}`} onClick={() => setTabela("semanal")}>
|
||||
<i className="fa-solid fa-calendar-day"></i> Semana
|
||||
</button>
|
||||
<button className={`btn-selecionar-tabelames ${tabela === 'mensal' ? 'ativo' : ''}`} onClick={() => setTabela("mensal")}>
|
||||
<i className="fa-solid fa-calendar-day"></i> Mês
|
||||
</button>
|
||||
</div>
|
||||
<div className='legenda-tabela'>
|
||||
<div className='legenda-item-realizado'><span>Realizado</span></div>
|
||||
<div className='legenda-item-confirmado'><span>Confirmado</span></div>
|
||||
<div className='legenda-item-agendado'><span>Agendado</span></div>
|
||||
<div className='legenda-item-cancelado'><span>Cancelado</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{tabela === "diario" && <TabelaAgendamentoDia handleClickAgendamento={handleClickAgendamento} agendamentos={filteredAgendamentos} />}
|
||||
{tabela === 'semanal' && <TabelaAgendamentoSemana agendamentos={filteredAgendamentos} />}
|
||||
{tabela === 'mensal' && <TabelaAgendamentoMes ListarDiasdoMes={ListarDiasdoMes} aplicarCores={true} agendamentos={filteredAgendamentos} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
:
|
||||
(
|
||||
<div className="fila-container">
|
||||
<div className="fila-header">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Pesquisar na fila de espera..."
|
||||
className="busca-fila-espera"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<h2 className="fila-titulo">Fila de Espera</h2>
|
||||
</div>
|
||||
<table className="fila-tabela">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Email</th>
|
||||
<th>CPF</th>
|
||||
<th>Telefone</th>
|
||||
<th>Entrou na fila de espera</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredFila.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td>{item.nome}</td>
|
||||
<td>{item.email}</td>
|
||||
<td>{item.cpf}</td>
|
||||
<td>{item.telefone}</td>
|
||||
<td>{item.entrada}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
) : (
|
||||
<FormNovaConsulta onCancel={handleClickCancel} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Agendamento;
|
||||
520
src/PagesMedico/DoctorAgendamentoManager.jsx
Normal file
520
src/PagesMedico/DoctorAgendamentoManager.jsx
Normal file
@ -0,0 +1,520 @@
|
||||
import React, { useState, useMemo, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import API_KEY from '../components/utils/apiKeys.js';
|
||||
import AgendamentoCadastroManager from '../pages/AgendamentoCadastroManager.jsx';
|
||||
import TabelaAgendamentoDia from '../components/AgendarConsulta/TabelaAgendamentoDia';
|
||||
import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana';
|
||||
import TabelaAgendamentoMes from '../components/AgendarConsulta/TabelaAgendamentoMes';
|
||||
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta';
|
||||
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient.js';
|
||||
import { GetAllDoctors, GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor.js';
|
||||
|
||||
import { useAuth } from '../components/utils/AuthProvider.js';
|
||||
// ✨ NOVO: Caminho de importação corrigido com base na sua estrutura de pastas
|
||||
import AgendamentosMes from '../components/AgendarConsulta/DadosConsultasMock.js';
|
||||
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import "../pages/style/Agendamento.css";
|
||||
import '../pages/style/FilaEspera.css';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
|
||||
|
||||
const Agendamento = ({setDictInfo}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [selectedID, setSelectedId] = useState('0')
|
||||
const [filaEsperaData, setfilaEsperaData] = useState([])
|
||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||
const [tabela, setTabela] = useState('diario');
|
||||
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [agendamentos, setAgendamentos] = useState()
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({})
|
||||
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState()
|
||||
|
||||
const [ListaDeMedicos, setListaDeMedicos] = useState([])
|
||||
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([])
|
||||
const [searchTermDoctor, setSearchTermDoctor] = useState('');
|
||||
|
||||
|
||||
let authHeader = getAuthorizationHeader()
|
||||
|
||||
const FiltrarAgendamentos = async (listaTodosAgendamentos) => {
|
||||
const ConfigurarFiladeEspera = async (patient_id, doctor_id, agendamento) => {
|
||||
// Assumindo que GetDoctorByID e GetPatientByID estão definidos no seu escopo
|
||||
let medico = await GetDoctorByID(doctor_id, authHeader);
|
||||
let paciente = await GetPatientByID(patient_id, authHeader);
|
||||
|
||||
let dicionario = {
|
||||
agendamento: agendamento,
|
||||
Infos: {
|
||||
nome_nedico: medico.full_name,
|
||||
doctor_id: medico.id,
|
||||
patient_id: paciente[0].id,
|
||||
paciente_nome: paciente[0].full_name,
|
||||
paciente_cpf: paciente[0].cpf
|
||||
}
|
||||
};
|
||||
return dicionario;
|
||||
};
|
||||
|
||||
let DictAgendamentosOrganizados = {};
|
||||
let ListaFilaDeEspera = [];
|
||||
|
||||
// 1. Agrupamento (igual ao seu código original)
|
||||
for (const agendamento of listaTodosAgendamentos) {
|
||||
if (agendamento.status === 'requested') {
|
||||
// Recomenda-se usar Promise.all para melhorar a performance
|
||||
// mas, para manter a estrutura, mantemos o await no loop.
|
||||
let v = await ConfigurarFiladeEspera(agendamento.patient_id, agendamento.doctor_id, agendamento);
|
||||
ListaFilaDeEspera.push(v);
|
||||
} else {
|
||||
const DiaAgendamento = agendamento.scheduled_at.split("T")[0];
|
||||
|
||||
if (DiaAgendamento in DictAgendamentosOrganizados) {
|
||||
DictAgendamentosOrganizados[DiaAgendamento].push(agendamento);
|
||||
} else {
|
||||
DictAgendamentosOrganizados[DiaAgendamento] = [agendamento];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 2. Ordenação Interna: Ordenar os agendamentos por HORÁRIO (do menor para o maior)
|
||||
for (const DiaAgendamento in DictAgendamentosOrganizados) {
|
||||
DictAgendamentosOrganizados[DiaAgendamento].sort((a, b) => {
|
||||
// Compara as strings de data/hora (ISO 8601) diretamente,
|
||||
// que funcionam para ordenação cronológica.
|
||||
if (a.scheduled_at < b.scheduled_at) return -1;
|
||||
if (a.scheduled_at > b.scheduled_at) return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 3. Ordenação Externa: Ordenar os DIAS (as chaves do objeto)
|
||||
// Para garantir que as chaves fiquem na sequência cronológica correta.
|
||||
|
||||
// Pega as chaves (datas)
|
||||
const chavesOrdenadas = Object.keys(DictAgendamentosOrganizados).sort((a, b) => {
|
||||
// Compara as chaves de data (strings 'YYYY-MM-DD')
|
||||
if (a < b) return -1;
|
||||
if (a > b) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Cria um novo objeto no formato desejado, garantindo a ordem das chaves
|
||||
let DictAgendamentosFinal = {};
|
||||
for (const data of chavesOrdenadas) {
|
||||
DictAgendamentosFinal[data] = DictAgendamentosOrganizados[data];
|
||||
}
|
||||
setAgendamentosOrganizados(DictAgendamentosFinal); // Use o objeto final ordenado
|
||||
setfilaEsperaData(ListaFilaDeEspera);
|
||||
};
|
||||
|
||||
// Requisição inicial para mostrar os agendamentos do banco de dados
|
||||
useEffect(() => {
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("apikey", API_KEY)
|
||||
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
headers: myHeaders,
|
||||
redirect: 'follow'
|
||||
};
|
||||
|
||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions)
|
||||
.then(response => response.json())
|
||||
.then(result => {FiltrarAgendamentos(result);})
|
||||
.catch(error => console.log('error', error));
|
||||
|
||||
const PegarTodosOsMedicos = async () => {
|
||||
let lista = []
|
||||
const TodosOsMedicos = await GetAllDoctors(authHeader)
|
||||
|
||||
for(let d = 0; TodosOsMedicos.length > d; d++){
|
||||
lista.push({nomeMedico: TodosOsMedicos[d].full_name, idMedico: TodosOsMedicos[d].id })}
|
||||
setListaDeMedicos(lista)
|
||||
}
|
||||
PegarTodosOsMedicos()
|
||||
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
console.log("mudou FiltredTodosMedicos:", FiltredTodosMedicos);
|
||||
if (FiltredTodosMedicos.length === 1) {
|
||||
const unicoMedico = FiltredTodosMedicos[0];
|
||||
console.log(unicoMedico)
|
||||
const idMedicoFiltrado = unicoMedico.idMedico;
|
||||
console.log(`Médico único encontrado: ${unicoMedico.nomeMedico}. ID: ${idMedicoFiltrado}`);
|
||||
|
||||
const agendamentosDoMedico = filtrarAgendamentosPorMedico(
|
||||
DictAgendamentosOrganizados,
|
||||
idMedicoFiltrado
|
||||
);
|
||||
console.log(`Total de agendamentos filtrados para este médico: ${agendamentosDoMedico.length}`);
|
||||
console.log("Lista completa de Agendamentos do Médico:", agendamentosDoMedico);
|
||||
FiltrarAgendamentos(agendamentosDoMedico)
|
||||
|
||||
}
|
||||
}, [FiltredTodosMedicos]);
|
||||
|
||||
const deleteConsulta = (selectedPatientId) => {
|
||||
console.log("tentando apagar")
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("apikey", API_KEY)
|
||||
|
||||
var requestOptions = {
|
||||
method: 'DELETE',
|
||||
redirect: 'follow',
|
||||
headers: myHeaders
|
||||
};
|
||||
|
||||
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${selectedPatientId}`, requestOptions)
|
||||
.then(response => response.json())
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.log('error', error));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filtra todos os agendamentos em um objeto aninhado (data -> [agendamentos])
|
||||
* com base no ID do médico.
|
||||
*
|
||||
* @param {Object} dictAgendamentos - O dicionário de agendamentos.
|
||||
* @param {string} idMedicoFiltrado - O ID do médico (doctor_id) para ser usado como filtro.
|
||||
* @returns {Array} Um array contendo todos os agendamentos que correspondem ao idMedicoFiltrado.
|
||||
*/
|
||||
const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
||||
|
||||
// O corpo da função deve usar esses nomes de variáveis:
|
||||
const todasAsListasDeAgendamentos = Object.values(dictAgendamentos);
|
||||
|
||||
const todosOsAgendamentos = todasAsListasDeAgendamentos.flat();
|
||||
|
||||
const agendamentosFiltrados = todosOsAgendamentos.filter(agendamento =>
|
||||
agendamento.doctor_id === idMedicoFiltrado
|
||||
);
|
||||
|
||||
return agendamentosFiltrados;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Lógica para filtrar os dados da AGENDA (AgendamentosMes)
|
||||
const filteredAgendamentos = useMemo(() => {
|
||||
if (!searchTerm.trim()) {
|
||||
return AgendamentosMes;
|
||||
}
|
||||
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
||||
const filteredData = {};
|
||||
|
||||
for (const semana in AgendamentosMes) {
|
||||
filteredData[semana] = {};
|
||||
for (const dia in AgendamentosMes[semana]) {
|
||||
filteredData[semana][dia] = AgendamentosMes[semana][dia].filter(agendamento =>
|
||||
agendamento.status === 'vazio' ||
|
||||
(agendamento.paciente && agendamento.paciente.toLowerCase().includes(lowerCaseSearchTerm))
|
||||
);
|
||||
}
|
||||
}
|
||||
return filteredData;
|
||||
}, [searchTerm]);
|
||||
|
||||
const ListarDiasdoMes = (ano, mes) => {
|
||||
let segundas = []; let tercas = []; let quartas = []; let quintas = []; let sextas = []
|
||||
const base = dayjs(`${ano}-${mes}-01`)
|
||||
const DiasnoMes = base.daysInMonth()
|
||||
for (let d = 1; d <= DiasnoMes; d++) {
|
||||
const data = dayjs(`${ano}-${mes}-${d}`)
|
||||
const dia = data.format('dddd')
|
||||
switch (dia) {
|
||||
case 'Monday': segundas.push(d); break
|
||||
case 'Tuesday': tercas.push(d); break
|
||||
case 'Wednesday': quartas.push(d); break
|
||||
case 'Thursday': quintas.push(d); break
|
||||
case 'Friday': sextas.push(d); break
|
||||
default: break
|
||||
}
|
||||
}
|
||||
let ListaDiasDatas = {segundas:segundas,tercas:tercas,quartas: quartas,quintas: quintas,sextas: sextas}
|
||||
return ListaDiasDatas
|
||||
}
|
||||
|
||||
const handleClickAgendamento = (agendamento) => {
|
||||
if (agendamento.status !== 'vazio') return
|
||||
else setPageConsulta(true)
|
||||
};
|
||||
|
||||
|
||||
const handleSearchMedicos = (term) => {
|
||||
setSearchTermDoctor(term);
|
||||
if (term.trim() === '') {
|
||||
setFiltredTodosMedicos([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Lógica simples de filtragem:
|
||||
const filtered = ListaDeMedicos.filter(medico =>
|
||||
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
||||
);
|
||||
setFiltredTodosMedicos(filtered);
|
||||
};
|
||||
|
||||
|
||||
const handleClickCancel = () => setPageConsulta(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Agendar nova consulta</h1>
|
||||
|
||||
<button className='manage-button btn' onClick={() => navigate('/secretaria/excecoes-disponibilidade')}>
|
||||
<i className="bi bi-gear-fill me-1"></i>
|
||||
Mudar Disponibilidade
|
||||
</button>
|
||||
|
||||
<button className="btn btn-primary" onClick={() => setPageConsulta(true)}>
|
||||
<i className="bi bi-plus-circle"></i> Adicionar Paciente
|
||||
</button>
|
||||
|
||||
{!PageNovaConsulta ? (
|
||||
<div className='atendimento-eprocura'>
|
||||
|
||||
<div className='busca-atendimento-container'>
|
||||
|
||||
<div className='input-e-dropdown-wrapper'>
|
||||
|
||||
<div className='busca-atendimento'>
|
||||
<div>
|
||||
<i className="fa-solid fa-calendar-day"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filtrar atendimento por médico..."
|
||||
value={searchTermDoctor}
|
||||
onChange={(e) => handleSearchMedicos(e.target.value)} // Chama a nova função de filtro
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* DROPDOWN (RENDERIZAÇÃO CONDICIONAL) */}
|
||||
{searchTermDoctor && FiltredTodosMedicos.length > 0 && (
|
||||
<div className='dropdown-medicos'>
|
||||
{FiltredTodosMedicos.map((medico) => (
|
||||
<div
|
||||
key={medico.id}
|
||||
className='dropdown-item'
|
||||
onClick={() => {
|
||||
// Ação ao selecionar o médico
|
||||
setSearchTermDoctor(medico.nomeMedico); // Preenche o input
|
||||
//setFiltredTodosMedicos([]); // Fecha o dropdown
|
||||
// Lógica adicional, como selecionar o ID do médico...
|
||||
}}
|
||||
>
|
||||
<p>{medico.nomeMedico} </p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='unidade-selecionarprofissional'>
|
||||
<select>
|
||||
<option value="" disabled selected >Unidade</option>
|
||||
<option value="">Unidade Central</option>
|
||||
<option value="">Unidade Zona Norte</option>
|
||||
<option value="">Unidade Zona Oeste</option>
|
||||
</select>
|
||||
<input type="text" placeholder='Selecionar profissional' />
|
||||
</div>
|
||||
|
||||
<div className='container-btns-agenda-fila_esepera'>
|
||||
<button
|
||||
className={`btn-agenda ${FiladeEspera === false ? "opc-agenda-ativo" : ""}`}
|
||||
onClick={() => {
|
||||
setFiladeEspera(false);
|
||||
setSearchTerm('');
|
||||
}}
|
||||
>
|
||||
Agenda
|
||||
</button>
|
||||
<button
|
||||
className={`btn-fila-espera ${FiladeEspera === true ? "opc-filaespera-ativo" : ""}`}
|
||||
onClick={() => {
|
||||
setFiladeEspera(true);
|
||||
setSearchTerm('');
|
||||
}}
|
||||
>
|
||||
Fila de espera
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section className='calendario-ou-filaespera'>
|
||||
{FiladeEspera === false ?
|
||||
(
|
||||
<div className='calendario'>
|
||||
<div>
|
||||
<section className='btns-e-legenda-container'>
|
||||
<div>
|
||||
<button className={`btn-selecionar-tabeladia ${tabela === "diario" ? "ativo" : ""}`} onClick={() => setTabela("diario")}>
|
||||
<i className="fa-solid fa-calendar-day"></i> Dia
|
||||
</button>
|
||||
<button className={`btn-selecionar-tabelasemana ${tabela === 'semanal' ? 'ativo' : ""}`} onClick={() => setTabela("semanal")}>
|
||||
<i className="fa-solid fa-calendar-day"></i> Semana
|
||||
</button>
|
||||
<button className={`btn-selecionar-tabelames ${tabela === 'mensal' ? 'ativo' : ''}`} onClick={() => setTabela("mensal")}>
|
||||
<i className="fa-solid fa-calendar-day"></i> Mês
|
||||
</button>
|
||||
</div>
|
||||
<div className='legenda-tabela'>
|
||||
<div className='legenda-item-realizado'><span>Realizado</span></div>
|
||||
<div className='legenda-item-confirmado'><span>Confirmado</span></div>
|
||||
<div className='legenda-item-agendado'><span>Agendado</span></div>
|
||||
<div className='legenda-item-cancelado'><span>Cancelado</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{tabela === "diario" && <TabelaAgendamentoDia handleClickAgendamento={handleClickAgendamento} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />}
|
||||
{tabela === 'semanal' && <TabelaAgendamentoSemana agendamentos={DictAgendamentosOrganizados} ListarDiasdoMes={ListarDiasdoMes} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>}
|
||||
{tabela === 'mensal' && <TabelaAgendamentoMes ListarDiasdoMes={ListarDiasdoMes} aplicarCores={true} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
:
|
||||
(
|
||||
<div className="fila-container">
|
||||
<div className="fila-header">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Pesquisar na fila de espera..."
|
||||
className="busca-fila-espera"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<h2 className="fila-titulo">Fila de Espera</h2>
|
||||
</div>
|
||||
<table className="fila-tabela">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Telefone</th>
|
||||
|
||||
<th>Telefone</th>
|
||||
<th>Entrou na fila de espera</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filaEsperaData.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td> <p>{item.Infos?.paciente_nome} </p> </td>
|
||||
<td><p>{} </p></td>
|
||||
<td>{}</td>
|
||||
<td>{}</td>
|
||||
<td> <div className="d-flex gap-2">
|
||||
|
||||
<button className="btn btn-sm btn-edit"
|
||||
onClick={() => {
|
||||
console.log(item, 'item')
|
||||
navigate(`${2}/edit`)
|
||||
setDictInfo(item)
|
||||
}}
|
||||
>
|
||||
<i className="bi bi-pencil me-1"></i> Editar
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
className="btn btn-sm btn-delete"
|
||||
onClick={() => {
|
||||
setSelectedId(item.agendamento.id)
|
||||
setShowDeleteModal(true);
|
||||
}}
|
||||
>
|
||||
<i className="bi bi-trash me-1"></i> Excluir
|
||||
</button>
|
||||
</div></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
) : (
|
||||
<AgendamentoCadastroManager setPageConsulta={setPageConsulta} />
|
||||
)}
|
||||
|
||||
{showDeleteModal && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
style={{
|
||||
display: "block",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
}}
|
||||
tabIndex="-1"
|
||||
onClick={(e) =>
|
||||
e.target.classList.contains("modal") && setShowDeleteModal(false)
|
||||
}
|
||||
>
|
||||
<div className="modal-dialog modal-dialog-centered">
|
||||
<div className="modal-content">
|
||||
|
||||
<div className="modal-header bg-danger bg-opacity-25">
|
||||
<h5 className="modal-title text-danger">
|
||||
Confirmação de Exclusão
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
onClick={() => setShowDeleteModal(false)}
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<div className="modal-body">
|
||||
<p className="mb-0 fs-5">
|
||||
Tem certeza que deseja excluir este agendamento?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="modal-footer">
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={() => setShowDeleteModal(false)}
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-danger"
|
||||
onClick={() => {deleteConsulta(selectedID);setShowDeleteModal(false)}}
|
||||
|
||||
>
|
||||
<i className="bi bi-trash me-1"></i> Excluir
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>)}
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Agendamento;
|
||||
70
src/PagesPaciente/CardConsultaPaciente.jsx
Normal file
70
src/PagesPaciente/CardConsultaPaciente.jsx
Normal file
@ -0,0 +1,70 @@
|
||||
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'
|
||||
|
||||
const CardConsultaPaciente = ({consulta}) => {
|
||||
|
||||
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)
|
||||
|
||||
return (
|
||||
<div class="card-consulta">
|
||||
<div class="horario-container">
|
||||
|
||||
<span class="horario">
|
||||
{`${Data?.split("-")[2]}/${Data?.split("-")[1]}`}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-container">
|
||||
<span class="informacao">
|
||||
Dr {Medico?.full_name} - {Medico?.specialty}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardConsultaPaciente
|
||||
81
src/PagesPaciente/ConsultaCadastroManager.jsx
Normal file
81
src/PagesPaciente/ConsultaCadastroManager.jsx
Normal file
@ -0,0 +1,81 @@
|
||||
import React from 'react'
|
||||
import FormConsultaPaciente from './FormConsultaPaciente'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useAuth } from '../components/utils/AuthProvider'
|
||||
import API_KEY from '../components/utils/apiKeys'
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import { UserInfos } from '../components/utils/Functions-Endpoints/General'
|
||||
const ConsultaCadastroManager = () => {
|
||||
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
const [Dict, setDict] = useState({})
|
||||
const navigate = useNavigate()
|
||||
const [idUsuario, setIDusuario] = useState("")
|
||||
|
||||
let authHeader = getAuthorizationHeader()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const ColherInfoUsuario =async () => {
|
||||
const result = await UserInfos(authHeader)
|
||||
|
||||
setIDusuario(result?.profile?.id)
|
||||
|
||||
}
|
||||
ColherInfoUsuario()
|
||||
|
||||
|
||||
}, [])
|
||||
|
||||
const handleSave = (Dict) => {
|
||||
let DataAtual = dayjs()
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("apikey", API_KEY);
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
|
||||
var raw = JSON.stringify({
|
||||
"patient_id": Dict.patient_id,
|
||||
"doctor_id": Dict.doctor_id,
|
||||
"scheduled_at": `${Dict.dataAtendimento}T${Dict.horarioInicio}:00.000Z`,
|
||||
"duration_minutes": 30,
|
||||
"appointment_type": Dict.tipo_consulta,
|
||||
|
||||
"patient_notes": "Prefiro horário pela manhã",
|
||||
"insurance_provider": Dict.convenio,
|
||||
"status": Dict.status,
|
||||
"created_by": idUsuario
|
||||
});
|
||||
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
headers: myHeaders,
|
||||
body: raw,
|
||||
redirect: 'follow'
|
||||
};
|
||||
|
||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments", requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.log('error', error));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<FormConsultaPaciente agendamento={Dict} setAgendamento={setDict} onSave={handleSave} onCancel={() => navigate("/paciente/agendamento/")}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConsultaCadastroManager
|
||||
96
src/PagesPaciente/ConsultasPaciente.jsx
Normal file
96
src/PagesPaciente/ConsultasPaciente.jsx
Normal file
@ -0,0 +1,96 @@
|
||||
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 = () => {
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
|
||||
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 consultas = [
|
||||
{
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"doctor_id": "eaca4372-17bc-4905-9eff-7aeda46157b4",
|
||||
"patient_id": "3854866a-5476-48be-8313-77029ccdd7a7",
|
||||
"scheduled_at": "2019-08-24T14:15:22Z",
|
||||
"status": "string"
|
||||
}
|
||||
]*/
|
||||
|
||||
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}/>
|
||||
|
||||
))}
|
||||
|
||||
|
||||
<h2>Historico de consultas:</h2>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConsultasPaciente
|
||||
318
src/PagesPaciente/FormConsultaPaciente.jsx
Normal file
318
src/PagesPaciente/FormConsultaPaciente.jsx
Normal file
@ -0,0 +1,318 @@
|
||||
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";
|
||||
|
||||
const FormConsultaPaciente = ({ onCancel, onSave, setAgendamento, agendamento }) => {
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
|
||||
console.log(agendamento, 'aqui2')
|
||||
|
||||
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!");
|
||||
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;
|
||||
44
src/PagesPaciente/style.css
Normal file
44
src/PagesPaciente/style.css
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
/* 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 */
|
||||
}
|
||||
|
||||
.informacao {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
@ -3,10 +3,12 @@ import { GetPatientByID } from '../utils/Functions-Endpoints/Patient';
|
||||
import { useAuth } from '../utils/AuthProvider';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import "./style/card-consulta.css"
|
||||
const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, setDictInfo, setSelectedId} ) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
console.log(DadosConsulta, "AQUIIII")
|
||||
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
const authHeader = getAuthorizationHeader()
|
||||
const [Paciente, setPaciente] = useState()
|
||||
@ -48,7 +50,7 @@ const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, se
|
||||
console.log(DadosConsulta.status)
|
||||
|
||||
return (
|
||||
<div className={`container-cardconsulta-${TabelaAgendamento}`}>
|
||||
<div className={`container-cardconsulta container-cardconsulta-${TabelaAgendamento}`}>
|
||||
|
||||
{DadosConsulta.id?
|
||||
|
||||
@ -65,23 +67,18 @@ const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, se
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div className='container-botons'>
|
||||
<div className='actions-container'>
|
||||
<button className="btn btn-sm btn-edit-custom"
|
||||
|
||||
onClick={() => {navigate(`2/edit`)
|
||||
setDictInfo({agendamento:DadosConsulta, Infos:{paciente_cpf:Paciente.cpf, paciente_nome:Paciente.full_name}})
|
||||
|
||||
|
||||
setDictInfo({...DadosConsulta,paciente_cpf:Paciente.cpf, paciente_nome:Paciente.full_name, nome_medico:Medico.full_name})
|
||||
}}
|
||||
|
||||
>
|
||||
<i className="bi bi-pencil me-1"></i>
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-delete-custom"
|
||||
className="btn btn-sm btn-delete-custom-style "
|
||||
onClick={() => {
|
||||
console.log(DadosConsulta.id)
|
||||
setSelectedId(DadosConsulta.id);
|
||||
|
||||
@ -1,177 +0,0 @@
|
||||
import InputMask from "react-input-mask";
|
||||
import "./style/formagendamentos.css";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const FormNovaDisponibilidade = ({ onCancel, doctorID }) => {
|
||||
|
||||
const [dadosAtendimento, setDadosAtendimento] = useState({
|
||||
profissional: '',
|
||||
tipoAtendimento: '',
|
||||
dataAtendimento: '',
|
||||
inicio: '',
|
||||
termino: '',
|
||||
motivo: ''
|
||||
});
|
||||
|
||||
const handleAtendimentoChange = (e) => {
|
||||
const { value, name } = e.target;
|
||||
setDadosAtendimento(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmitExcecao = async (e) => {
|
||||
e.preventDefault();
|
||||
console.log("Modo Emergência Ativado: Tentando criar Exceção com novo endpoint.");
|
||||
|
||||
const { profissional, dataAtendimento, tipoAtendimento, inicio, termino, motivo } = dadosAtendimento;
|
||||
|
||||
if (!profissional || !dataAtendimento || !tipoAtendimento) {
|
||||
alert("Por favor, preencha o Profissional, Data, e Tipo da exceção.");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
doctor_id: profissional,
|
||||
date: dataAtendimento,
|
||||
start_time: inicio + ":00" || null, // Adiciona ":00" se o input type="time" retornar apenas HH:MM
|
||||
end_time: termino + ":00" || null, // Adiciona ":00"
|
||||
kind: tipoAtendimento,
|
||||
reason: motivo,
|
||||
};
|
||||
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
headers: myHeaders,
|
||||
body: JSON.stringify(payload),
|
||||
redirect: 'follow'
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_exceptions", requestOptions);
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok || response.status === 201) {
|
||||
console.log("Exceção de emergência criada com sucesso:", result);
|
||||
alert(`Consulta de emergência agendada como exceção! Detalhes: ${JSON.stringify(result)}`);
|
||||
} else {
|
||||
console.error("Erro ao criar exceção de emergência:", result);
|
||||
alert(`Erro ao agendar exceção. Status: ${response.status}. Detalhes: ${result.message || JSON.stringify(result)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro na requisição para criar exceção:", error);
|
||||
alert("Erro de comunicação com o servidor ou formato de resposta inválido.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-container">
|
||||
<form className="form-agendamento" onSubmit={handleSubmitExcecao}>
|
||||
<h2 className="section-title">Informações do médico</h2>
|
||||
|
||||
<div className="campo-informacoes-atendimento">
|
||||
|
||||
<div className="campo-de-input">
|
||||
<label>ID do profissional *</label>
|
||||
<input
|
||||
type="text"
|
||||
name="profissional"
|
||||
required
|
||||
value={dadosAtendimento.profissional}
|
||||
onChange={handleAtendimentoChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="campo-de-input">
|
||||
<label>Tipo de exceção *</label>
|
||||
<select name="tipoAtendimento" onChange={handleAtendimentoChange}>
|
||||
<option value="" disabled invisible selected>Selecione o tipo de exceção</option>
|
||||
<option value={dadosAtendimento.tipoAtendimento === "liberacao"} >Liberação</option>
|
||||
<option value={dadosAtendimento.tipoAtendimento === "bloqueio"} >Bloqueio</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"
|
||||
required
|
||||
value={dadosAtendimento.dataAtendimento}
|
||||
onChange={handleAtendimentoChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="campo-informacoes-atendimento">
|
||||
<div className="campo-de-input">
|
||||
<label>Início</label>
|
||||
<input
|
||||
type="time"
|
||||
name="inicio"
|
||||
value={dadosAtendimento.inicio}
|
||||
onChange={handleAtendimentoChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="campo-de-input">
|
||||
<label>Término</label>
|
||||
<input
|
||||
type="time"
|
||||
name="termino"
|
||||
value={dadosAtendimento.termino}
|
||||
onChange={handleAtendimentoChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="campo-de-input">
|
||||
<label>Profissional solicitante</label>
|
||||
<select
|
||||
name="solicitante"
|
||||
value={dadosAtendimento.solicitante}
|
||||
onChange={handleAtendimentoChange}
|
||||
>
|
||||
<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 className="informacoes-atendimento-segunda-linha-direita">
|
||||
|
||||
<div className="campo-de-input">
|
||||
<label>Motivo da exceção</label>
|
||||
<textarea
|
||||
name="motivo"
|
||||
rows="4"
|
||||
cols="1"
|
||||
value={dadosAtendimento.motivo}
|
||||
onChange={handleAtendimentoChange}
|
||||
></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 FormNovaDisponibilidade;
|
||||
@ -70,7 +70,7 @@ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
if (patientData) {
|
||||
setAgendamento((prev) => ({
|
||||
...prev,
|
||||
pacinte_nome: patientData.full_name,
|
||||
paciente_nome: patientData.full_name,
|
||||
patient_id: patientData.id
|
||||
}));
|
||||
}}
|
||||
@ -146,7 +146,7 @@ const handleSelectProfissional = async (profissional) => {
|
||||
setAgendamento(prev => ({
|
||||
...prev,
|
||||
doctor_id: profissional.id,
|
||||
medico_nome: profissional.full_name
|
||||
nome_medico: profissional.full_name
|
||||
}));
|
||||
// 2. Fecha o dropdown
|
||||
setProfissionaisFiltrados([]);
|
||||
@ -215,9 +215,9 @@ const handleSubmit = (e) => {
|
||||
<label>Nome do profissional *</label>
|
||||
<input
|
||||
type="text"
|
||||
name="medico_nome" // Use o nome correto da propriedade no estado `agendamento`
|
||||
name="nome_medico" // Use o nome correto da propriedade no estado `agendamento`
|
||||
onChange={handleSearchProfissional}
|
||||
value={agendamento.medico_nome}
|
||||
value={agendamento?.nome_medico}
|
||||
autoComplete="off" // Ajuda a evitar o autocomplete nativo do navegador
|
||||
required
|
||||
/>
|
||||
|
||||
@ -10,6 +10,7 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
|
||||
|
||||
let ListaDiasComAgendamentos = Object.keys(agendamentos)
|
||||
|
||||
console.log(agendamentos)
|
||||
|
||||
|
||||
//console.log(Dia, "hshdhshhsdhs")
|
||||
@ -33,7 +34,7 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
|
||||
<table className='tabeladiaria'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Horário</th>
|
||||
<th className='cabecalho-horario'>Horário</th>
|
||||
<th>{}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -46,7 +47,7 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
|
||||
return(
|
||||
<tr key={index}>
|
||||
|
||||
<td><p>{`${horario[0]}:${horario[1]}`}</p></td>
|
||||
<td className='coluna-horario'><p className='horario-texto'>{`${horario[0]}:${horario[1]}`}</p></td>
|
||||
<td className='mostrar-horario'>
|
||||
<div onClick={() => handleClickAgendamento(agendamento)}>
|
||||
<CardConsulta DadosConsulta={agendamento} TabelaAgendamento={'dia'} setShowDeleteModal={setShowDeleteModal} setDictInfo={setDictInfo} setSelectedId={setSelectedId}/>
|
||||
|
||||
61
src/components/AgendarConsulta/style/card-consulta.css
Normal file
61
src/components/AgendarConsulta/style/card-consulta.css
Normal file
@ -0,0 +1,61 @@
|
||||
.actions-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 10px;
|
||||
margin-left: 2rem;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
||||
/* 🎨 Glassmorphism */
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
backdrop-filter: blur(80px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Mostra no hover do card */
|
||||
.container-cardconsulta:hover .actions-container {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 3. Estilos base para o botão de edição (amarelo) */
|
||||
.btn-edit-custom-style {
|
||||
background-color: #ffc107; /* Amarelo da sua imagem */
|
||||
color: #343a40; /* Cor do ícone (cinza escuro para contraste) */
|
||||
border: none;
|
||||
padding: 8px 12px; /* Ajuste o padding para o tamanho do botão */
|
||||
border-radius: 0.25rem; /* Leve arredondamento de borda */
|
||||
transition: background-color 0.2s ease-in-out; /* Suaviza a transição de cor */
|
||||
}
|
||||
|
||||
|
||||
/* 5. Estilos base para o botão de exclusão (vermelho) */
|
||||
.btn-delete-custom-style {
|
||||
background-color: #dc3545; /* Vermelho da sua imagem */
|
||||
color: #ffffff; /* Cor do ícone (branco para contraste) */
|
||||
border: none;
|
||||
padding: 8px 12px; /* Ajuste o padding para o tamanho do botão */
|
||||
border-radius: 0.25rem; /* Leve arredondamento de borda */
|
||||
transition: background-color 0.2s ease-in-out; /* Suaviza a transição de cor */
|
||||
|
||||
font-weight:bold ;
|
||||
|
||||
}
|
||||
|
||||
/* 6. Estilo de hover para o botão de exclusão */
|
||||
.btn-delete-custom-style:hover {
|
||||
background-color: #c82333; /* Um vermelho um pouco mais escuro para o hover */
|
||||
filter: brightness(90%); /* Alternativa: escurecer um pouco mais */
|
||||
}
|
||||
|
||||
/* 7. Estilos para os ícones dentro dos botões (já está no JSX com fs-4) */
|
||||
/* .fs-4 do Bootstrap já cuida do tamanho do ícone. Se precisar de mais controle, adicione aqui. */
|
||||
.action-button .bi {
|
||||
/* Exemplo: se precisar de um ajuste fino além do fs-4 */
|
||||
/* font-size: 1.5rem; */
|
||||
}
|
||||
@ -7,6 +7,47 @@
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
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 */
|
||||
.tabeladiaria th, .tabeladiaria td {
|
||||
@ -51,7 +92,6 @@ font-weight: 600;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Ajuste para a classe calendario, se for usada */
|
||||
|
||||
|
||||
|
||||
|
||||
184
src/components/FormCriarExcecao.jsx
Normal file
184
src/components/FormCriarExcecao.jsx
Normal file
@ -0,0 +1,184 @@
|
||||
// src/components/FormCriarExcecao.jsx
|
||||
|
||||
import React, { useState } from "react";
|
||||
// Assumindo que você usa o mesmo estilo
|
||||
import "./AgendarConsulta/style/formagendamentos.css";
|
||||
|
||||
const ENDPOINT_CRIAR_EXCECAO = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_exceptions";
|
||||
|
||||
const FormCriarExcecao = ({ onCancel, doctorID }) => {
|
||||
|
||||
const [dadosAtendimento, setDadosAtendimento] = useState({
|
||||
profissional: doctorID || '',
|
||||
tipoAtendimento: '',
|
||||
dataAtendimento: '',
|
||||
inicio: '',
|
||||
termino: '',
|
||||
motivo: ''
|
||||
});
|
||||
|
||||
const handleAtendimentoChange = (e) => {
|
||||
const { value, name } = e.target;
|
||||
setDadosAtendimento(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmitExcecao = async (e) => {
|
||||
e.preventDefault();
|
||||
console.log("Tentando criar Exceção.");
|
||||
|
||||
const { profissional, dataAtendimento, tipoAtendimento, inicio, termino, motivo } = dadosAtendimento;
|
||||
|
||||
// Validação
|
||||
if (!profissional || !dataAtendimento || !tipoAtendimento || !motivo) {
|
||||
alert("Por favor, preencha o ID do Profissional, Data, Tipo e Motivo.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Adiciona ":00" se o campo de hora estiver preenchido
|
||||
const startTime = inicio ? inicio + ":00" : undefined;
|
||||
const endTime = termino ? termino + ":00" : undefined;
|
||||
|
||||
const payload = {
|
||||
doctor_id: profissional,
|
||||
date: dataAtendimento,
|
||||
start_time: startTime,
|
||||
end_time: endTime,
|
||||
kind: tipoAtendimento,
|
||||
reason: motivo,
|
||||
};
|
||||
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
headers: myHeaders,
|
||||
body: JSON.stringify(payload),
|
||||
redirect: 'follow'
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(ENDPOINT_CRIAR_EXCECAO, requestOptions);
|
||||
const resultText = await response.text();
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(resultText);
|
||||
} catch {
|
||||
result = { message: resultText || 'Sucesso, mas resposta não é JSON.' };
|
||||
}
|
||||
|
||||
if (response.ok || response.status === 201) {
|
||||
console.log("Exceção criada com sucesso:", result);
|
||||
alert(`Exceção criada! Detalhes: ${result.id || JSON.stringify(result)}`);
|
||||
onCancel(true); // Indica sucesso para o componente pai recarregar
|
||||
} else {
|
||||
console.error("Erro ao criar exceção:", result);
|
||||
alert(`Erro ao criar exceção. Status: ${response.status}. Detalhes: ${result.message || JSON.stringify(result)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro na requisição para criar exceção:", error);
|
||||
alert("Erro de comunicação com o servidor.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-container">
|
||||
<form className="form-agendamento" onSubmit={handleSubmitExcecao}>
|
||||
<h2 className="section-title">Informações da Nova Exceção</h2>
|
||||
|
||||
<div className="campo-informacoes-atendimento">
|
||||
|
||||
<div className="campo-de-input">
|
||||
<label>ID do profissional *</label>
|
||||
<input
|
||||
type="text"
|
||||
name="profissional"
|
||||
required
|
||||
value={dadosAtendimento.profissional}
|
||||
onChange={handleAtendimentoChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="campo-de-input">
|
||||
<label>Tipo de exceção *</label>
|
||||
<select name="tipoAtendimento" onChange={handleAtendimentoChange} value={dadosAtendimento.tipoAtendimento} required>
|
||||
<option value="" disabled selected>Selecione o tipo de exceção</option>
|
||||
<option value="liberacao" >Liberação (Criar Slot)</option>
|
||||
<option value="bloqueio" >Bloqueio (Remover Slot)</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"
|
||||
required
|
||||
value={dadosAtendimento.dataAtendimento}
|
||||
onChange={handleAtendimentoChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="campo-informacoes-atendimento">
|
||||
<div className="campo-de-input">
|
||||
<label>Início (Opcional)</label>
|
||||
<input
|
||||
type="time"
|
||||
name="inicio"
|
||||
value={dadosAtendimento.inicio}
|
||||
onChange={handleAtendimentoChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="campo-de-input">
|
||||
<label>Término (Opcional)</label>
|
||||
<input
|
||||
type="time"
|
||||
name="termino"
|
||||
value={dadosAtendimento.termino}
|
||||
onChange={handleAtendimentoChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="campo-de-input">
|
||||
{/* Removendo o campo solicitante, pois não está no payload da API de exceções */}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="informacoes-atendimento-segunda-linha-direita">
|
||||
<div className="campo-de-input">
|
||||
<label>Motivo da exceção *</label>
|
||||
<textarea
|
||||
name="motivo"
|
||||
rows="4"
|
||||
cols="1"
|
||||
required
|
||||
value={dadosAtendimento.motivo}
|
||||
onChange={handleAtendimentoChange}
|
||||
></textarea>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<div className="form-actions">
|
||||
<button type="submit" className="btn-primary">Criar Exceção</button>
|
||||
<button type="button" className="btn-cancel" onClick={() => onCancel(false)}>Cancelar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormCriarExcecao;
|
||||
@ -1,56 +1,61 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Link, useNavigate, useLocation } from 'react-router-dom';
|
||||
import './DoctorForm.css';
|
||||
import React, { useState, useRef } from "react";
|
||||
import { Link, useNavigate, useLocation } from "react-router-dom";
|
||||
import "./DoctorForm.css";
|
||||
import HorariosDisponibilidade from "../doctors/HorariosDisponibilidade";
|
||||
|
||||
function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
|
||||
const FormatTelefones = (valor) => {
|
||||
const digits = String(valor).replace(/\D/g, '').slice(0, 11);
|
||||
const digits = String(valor).replace(/\D/g, "").slice(0, 11);
|
||||
return digits
|
||||
.replace(/(\d)/, '($1')
|
||||
.replace(/(\d{2})(\d)/, '$1) $2')
|
||||
.replace(/(\d)(\d{4})/, '$1 $2')
|
||||
.replace(/(\d{4})(\d{4})/, '$1-$2');
|
||||
.replace(/(\d)/, "($1")
|
||||
.replace(/(\d{2})(\d)/, "$1) $2")
|
||||
.replace(/(\d)(\d{4})/, "$1 $2")
|
||||
.replace(/(\d{4})(\d{4})/, "$1-$2");
|
||||
};
|
||||
|
||||
const FormatCPF = (valor) => {
|
||||
const digits = String(valor).replace(/\D/g, '').slice(0, 11);
|
||||
const digits = String(valor).replace(/\D/g, "").slice(0, 11);
|
||||
return digits
|
||||
.replace(/(\d{3})(\d)/, '$1.$2')
|
||||
.replace(/(\d{3})(\d)/, '$1.$2')
|
||||
.replace(/(\d{3})(\d{1,2})$/, '$1-$2');
|
||||
.replace(/(\d{3})(\d)/, "$1.$2")
|
||||
.replace(/(\d{3})(\d)/, "$1.$2")
|
||||
.replace(/(\d{3})(\d{1,2})$/, "$1-$2");
|
||||
};
|
||||
|
||||
const validarCPF = (cpf) => {
|
||||
const cpfLimpo = cpf.replace(/\D/g, '');
|
||||
|
||||
const cpfLimpo = cpf.replace(/\D/g, "");
|
||||
|
||||
if (cpfLimpo.length !== 11) return false;
|
||||
|
||||
if (/^(\d)\1+$/.test(cpfLimpo)) return false;
|
||||
|
||||
|
||||
let soma = 0;
|
||||
for (let i = 0; i < 9; i++) {
|
||||
soma += parseInt(cpfLimpo.charAt(i)) * (10 - i);
|
||||
}
|
||||
let resto = 11 - (soma % 11);
|
||||
let digito1 = resto === 10 || resto === 11 ? 0 : resto;
|
||||
|
||||
|
||||
soma = 0;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
soma += parseInt(cpfLimpo.charAt(i)) * (11 - i);
|
||||
}
|
||||
resto = 11 - (soma % 11);
|
||||
let digito2 = resto === 10 || resto === 11 ? 0 : resto;
|
||||
|
||||
return digito1 === parseInt(cpfLimpo.charAt(9)) && digito2 === parseInt(cpfLimpo.charAt(10));
|
||||
|
||||
return (
|
||||
digito1 === parseInt(cpfLimpo.charAt(9)) &&
|
||||
digito2 === parseInt(cpfLimpo.charAt(10))
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const [avatarUrl, setAvatarUrl] = useState(null);
|
||||
const [showRequiredModal, setShowRequiredModal] = useState(false);
|
||||
const [emptyFields, setEmptyFields] = useState([]);
|
||||
const [cpfError, setCpfError] = useState('');
|
||||
const [cpfError, setCpfError] = useState("");
|
||||
|
||||
const nomeRef = useRef(null);
|
||||
const cpfRef = useRef(null);
|
||||
@ -63,12 +68,13 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
dadosPessoais: true,
|
||||
contato: false,
|
||||
endereco: false,
|
||||
horarios: false,
|
||||
});
|
||||
|
||||
const handleToggleCollapse = (section) => {
|
||||
setCollapsedSections(prevState => ({
|
||||
setCollapsedSections((prevState) => ({
|
||||
...prevState,
|
||||
[section]: !prevState[section]
|
||||
[section]: !prevState[section],
|
||||
}));
|
||||
};
|
||||
|
||||
@ -76,50 +82,53 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
const { name, value, type, checked, files } = e.target;
|
||||
|
||||
if (value && emptyFields.includes(name)) {
|
||||
setEmptyFields(prev => prev.filter(field => field !== name));
|
||||
setEmptyFields((prev) => prev.filter((field) => field !== name));
|
||||
}
|
||||
|
||||
if (name === 'cpf' && cpfError) {
|
||||
setCpfError('');
|
||||
if (name === "cpf" && cpfError) {
|
||||
setCpfError("");
|
||||
}
|
||||
|
||||
if (type === 'checkbox') {
|
||||
setFormData(prev => ({ ...prev, [name]: checked }));
|
||||
} else if (type === 'file') {
|
||||
setFormData(prev => ({ ...prev, [name]: files[0] }));
|
||||
if (type === "checkbox") {
|
||||
setFormData((prev) => ({ ...prev, [name]: checked }));
|
||||
} else if (type === "file") {
|
||||
setFormData((prev) => ({ ...prev, [name]: files[0] }));
|
||||
|
||||
if (name === 'foto' && files[0]) {
|
||||
if (name === "foto" && files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setAvatarUrl(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(files[0]);
|
||||
} else if (name === 'foto' && !files[0]) {
|
||||
} else if (name === "foto" && !files[0]) {
|
||||
setAvatarUrl(null);
|
||||
}
|
||||
|
||||
} else if (name.includes('cpf')) {
|
||||
} else if (name.includes("cpf")) {
|
||||
let cpfFormatado = FormatCPF(value);
|
||||
setFormData(prev => ({ ...prev, [name]: cpfFormatado }));
|
||||
setFormData((prev) => ({ ...prev, [name]: cpfFormatado }));
|
||||
|
||||
const cpfLimpo = cpfFormatado.replace(/\D/g, '');
|
||||
const cpfLimpo = cpfFormatado.replace(/\D/g, "");
|
||||
if (cpfLimpo.length === 11) {
|
||||
if (!validarCPF(cpfFormatado)) {
|
||||
setCpfError('CPF inválido');
|
||||
setCpfError("CPF inválido");
|
||||
} else {
|
||||
setCpfError('');
|
||||
setCpfError("");
|
||||
}
|
||||
}
|
||||
} else if (name.includes('phone')) {
|
||||
} else if (name.includes("phone")) {
|
||||
let telefoneFormatado = FormatTelefones(value);
|
||||
setFormData(prev => ({ ...prev, [name]: telefoneFormatado }));
|
||||
setFormData((prev) => ({ ...prev, [name]: telefoneFormatado }));
|
||||
} else {
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleAvailabilityUpdate = (newAvailability) => {
|
||||
setFormData((prev) => ({ ...prev, availability: newAvailability }));
|
||||
};
|
||||
|
||||
const handleCepBlur = async () => {
|
||||
const cep = formData.cep?.replace(/\D/g, '');
|
||||
const cep = formData.cep?.replace(/\D/g, "");
|
||||
if (cep && cep.length === 8) {
|
||||
try {
|
||||
const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`);
|
||||
@ -127,49 +136,49 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
if (!data.erro) {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
street: data.logradouro || '',
|
||||
neighborhood: data.bairro || '',
|
||||
city: data.localidade || '',
|
||||
state: data.uf || ''
|
||||
street: data.logradouro || "",
|
||||
neighborhood: data.bairro || "",
|
||||
city: data.localidade || "",
|
||||
state: data.uf || "",
|
||||
}));
|
||||
} else {
|
||||
setShowRequiredModal(true);
|
||||
setEmptyFields(['cep']);
|
||||
setEmptyFields(["cep"]);
|
||||
}
|
||||
} catch (error) {
|
||||
setShowRequiredModal(true);
|
||||
setEmptyFields(['cep']);
|
||||
setEmptyFields(["cep"]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToEmptyField = (fieldName) => {
|
||||
let fieldRef = null;
|
||||
|
||||
|
||||
switch (fieldName) {
|
||||
case 'full_name':
|
||||
case "full_name":
|
||||
fieldRef = nomeRef;
|
||||
setCollapsedSections(prev => ({ ...prev, dadosPessoais: true }));
|
||||
setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true }));
|
||||
break;
|
||||
case 'cpf':
|
||||
case "cpf":
|
||||
fieldRef = cpfRef;
|
||||
setCollapsedSections(prev => ({ ...prev, dadosPessoais: true }));
|
||||
setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true }));
|
||||
break;
|
||||
case 'email':
|
||||
case "email":
|
||||
fieldRef = emailRef;
|
||||
setCollapsedSections(prev => ({ ...prev, contato: true }));
|
||||
setCollapsedSections((prev) => ({ ...prev, contato: true }));
|
||||
break;
|
||||
case 'phone_mobile':
|
||||
case "phone_mobile":
|
||||
fieldRef = telefoneRef;
|
||||
setCollapsedSections(prev => ({ ...prev, contato: true }));
|
||||
setCollapsedSections((prev) => ({ ...prev, contato: true }));
|
||||
break;
|
||||
case 'crm_uf':
|
||||
case "crm_uf":
|
||||
fieldRef = crmUfRef;
|
||||
setCollapsedSections(prev => ({ ...prev, dadosPessoais: true }));
|
||||
setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true }));
|
||||
break;
|
||||
case 'crm':
|
||||
case "crm":
|
||||
fieldRef = crmRef;
|
||||
setCollapsedSections(prev => ({ ...prev, dadosPessoais: true }));
|
||||
setCollapsedSections((prev) => ({ ...prev, dadosPessoais: true }));
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
@ -177,19 +186,20 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
|
||||
setTimeout(() => {
|
||||
if (fieldRef.current) {
|
||||
fieldRef.current.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center'
|
||||
fieldRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
fieldRef.current.focus();
|
||||
|
||||
fieldRef.current.style.border = '2px solid #dc3545';
|
||||
fieldRef.current.style.boxShadow = '0 0 0 0.2rem rgba(220, 53, 69, 0.25)';
|
||||
|
||||
|
||||
fieldRef.current.style.border = "2px solid #dc3545";
|
||||
fieldRef.current.style.boxShadow =
|
||||
"0 0 0 0.2rem rgba(220, 53, 69, 0.25)";
|
||||
|
||||
setTimeout(() => {
|
||||
if (fieldRef.current) {
|
||||
fieldRef.current.style.border = '';
|
||||
fieldRef.current.style.boxShadow = '';
|
||||
fieldRef.current.style.border = "";
|
||||
fieldRef.current.style.boxShadow = "";
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
@ -198,17 +208,17 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const missingFields = [];
|
||||
if (!formData.full_name) missingFields.push('full_name');
|
||||
if (!formData.cpf) missingFields.push('cpf');
|
||||
if (!formData.email) missingFields.push('email');
|
||||
if (!formData.phone_mobile) missingFields.push('phone_mobile');
|
||||
if (!formData.crm_uf) missingFields.push('crm_uf');
|
||||
if (!formData.crm) missingFields.push('crm');
|
||||
if (!formData.full_name) missingFields.push("full_name");
|
||||
if (!formData.cpf) missingFields.push("cpf");
|
||||
if (!formData.email) missingFields.push("email");
|
||||
if (!formData.phone_mobile) missingFields.push("phone_mobile");
|
||||
if (!formData.crm_uf) missingFields.push("crm_uf");
|
||||
if (!formData.crm) missingFields.push("crm");
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
setEmptyFields(missingFields);
|
||||
setShowRequiredModal(true);
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
if (missingFields.length > 0) {
|
||||
scrollToEmptyField(missingFields[0]);
|
||||
@ -217,20 +227,20 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cpfLimpo = formData.cpf.replace(/\D/g, '');
|
||||
const cpfLimpo = formData.cpf.replace(/\D/g, "");
|
||||
if (cpfLimpo.length !== 11) {
|
||||
setShowRequiredModal(true);
|
||||
setEmptyFields(['cpf']);
|
||||
setCpfError('CPF deve ter 11 dígitos');
|
||||
setTimeout(() => scrollToEmptyField('cpf'), 500);
|
||||
setEmptyFields(["cpf"]);
|
||||
setCpfError("CPF deve ter 11 dígitos");
|
||||
setTimeout(() => scrollToEmptyField("cpf"), 500);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validarCPF(formData.cpf)) {
|
||||
setShowRequiredModal(true);
|
||||
setEmptyFields(['cpf']);
|
||||
setCpfError('CPF inválido');
|
||||
setTimeout(() => scrollToEmptyField('cpf'), 500);
|
||||
setEmptyFields(["cpf"]);
|
||||
setCpfError("CPF inválido");
|
||||
setTimeout(() => scrollToEmptyField("cpf"), 500);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -245,35 +255,42 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
setShowRequiredModal(false);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modal de Alerta */}
|
||||
{showRequiredModal && (
|
||||
<div className="modal-overlay">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Atenção</h5>
|
||||
<button
|
||||
onClick={handleModalClose}
|
||||
className="modal-close-btn"
|
||||
>
|
||||
<button onClick={handleModalClose} className="modal-close-btn">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="modal-body">
|
||||
<p className="modal-message">
|
||||
{cpfError ? 'Problema com o CPF:' : 'Por favor, preencha:'}
|
||||
{cpfError ? "Problema com o CPF:" : "Por favor, preencha:"}
|
||||
</p>
|
||||
<div className="modal-list">
|
||||
{cpfError ? (
|
||||
<p className="modal-list-item">{cpfError}</p>
|
||||
) : (
|
||||
<>
|
||||
{!formData.full_name && <p className="modal-list-item">- Nome</p>}
|
||||
{!formData.full_name && (
|
||||
<p className="modal-list-item">- Nome</p>
|
||||
)}
|
||||
{!formData.cpf && <p className="modal-list-item">- CPF</p>}
|
||||
{!formData.email && <p className="modal-list-item">- Email</p>}
|
||||
{!formData.phone_mobile && <p className="modal-list-item">- Telefone</p>}
|
||||
{!formData.crm_uf && <p className="modal-list-item">- Estado do CRM</p>}
|
||||
{!formData.email && (
|
||||
<p className="modal-list-item">- Email</p>
|
||||
)}
|
||||
{!formData.phone_mobile && (
|
||||
<p className="modal-list-item">- Telefone</p>
|
||||
)}
|
||||
{!formData.crm_uf && (
|
||||
<p className="modal-list-item">- Estado do CRM</p>
|
||||
)}
|
||||
{!formData.crm && <p className="modal-list-item">- CRM</p>}
|
||||
</>
|
||||
)}
|
||||
@ -281,10 +298,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
</div>
|
||||
|
||||
<div className="modal-footer">
|
||||
<button
|
||||
onClick={handleModalClose}
|
||||
className="modal-confirm-btn"
|
||||
>
|
||||
<button onClick={handleModalClose} className="modal-confirm-btn">
|
||||
Fechar
|
||||
</button>
|
||||
</div>
|
||||
@ -292,19 +306,28 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Formulário Principal */}
|
||||
<div className="card doctor-form-container shadow-sm">
|
||||
<h3 className="doctor-form-title">MediConnect</h3>
|
||||
|
||||
{/* DADOS PESSOAIS */}
|
||||
<div className="form-section">
|
||||
<h4 className="section-header" onClick={() => handleToggleCollapse('dadosPessoais')}>
|
||||
<h4
|
||||
className="section-header"
|
||||
onClick={() => handleToggleCollapse("dadosPessoais")}
|
||||
>
|
||||
Dados Pessoais
|
||||
<span className="section-toggle">
|
||||
{collapsedSections.dadosPessoais ? '▲' : '▼'}
|
||||
{collapsedSections.dadosPessoais ? "▲" : "▼"}
|
||||
</span>
|
||||
</h4>
|
||||
<div className={`collapse${collapsedSections.dadosPessoais ? ' show' : ''}`}>
|
||||
<div
|
||||
className={`collapse${
|
||||
collapsedSections.dadosPessoais ? " show" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="row mt-3">
|
||||
{/* Foto / Avatar */}
|
||||
<div className="col-md-6 mb-3 avatar-container">
|
||||
<div className="me-3">
|
||||
{avatarUrl ? (
|
||||
@ -314,13 +337,16 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
className="avatar-image"
|
||||
/>
|
||||
) : (
|
||||
<div className="avatar-placeholder">
|
||||
☤
|
||||
</div>
|
||||
<div className="avatar-placeholder">☤</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="foto-input" className="btn btn-primary file-input-label">Carregar Foto</label>
|
||||
<label
|
||||
htmlFor="foto-input"
|
||||
className="btn btn-primary file-input-label"
|
||||
>
|
||||
Carregar Foto
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
className="form-control d-none"
|
||||
@ -329,49 +355,70 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
onChange={handleChange}
|
||||
accept="image/*"
|
||||
/>
|
||||
{formData.foto && <span className="ms-2 form-label">{formData.foto.name}</span>}
|
||||
{formData.foto && (
|
||||
<span className="ms-2 form-label">
|
||||
{formData.foto.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Nome Completo */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Nome: *</label>
|
||||
<input
|
||||
<input
|
||||
ref={nomeRef}
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="full_name"
|
||||
value={formData.full_name || ''}
|
||||
onChange={handleChange}
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="full_name"
|
||||
value={formData.full_name || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Data de Nascimento */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Data de nascimento:</label>
|
||||
<input type="date" className="form-control form-control-custom" name="birth_date" value={formData.birth_date || ''} onChange={handleChange} min="1900-01-01" max="2025-09-24" />
|
||||
<input
|
||||
type="date"
|
||||
className="form-control form-control-custom"
|
||||
name="birth_date"
|
||||
value={formData.birth_date || ""}
|
||||
onChange={handleChange}
|
||||
min="1900-01-01"
|
||||
max="2025-09-24"
|
||||
/>
|
||||
</div>
|
||||
{/* CPF */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">CPF: *</label>
|
||||
<input
|
||||
<input
|
||||
ref={cpfRef}
|
||||
type="text"
|
||||
className={`form-control form-control-custom ${cpfError ? 'is-invalid' : ''}`}
|
||||
name="cpf"
|
||||
value={formData.cpf || ''}
|
||||
onChange={handleChange}
|
||||
type="text"
|
||||
className={`form-control form-control-custom ${
|
||||
cpfError ? "is-invalid" : ""
|
||||
}`}
|
||||
name="cpf"
|
||||
value={formData.cpf || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{cpfError && (
|
||||
<div className="invalid-feedback" style={{ display: 'block' }}>
|
||||
<div
|
||||
className="invalid-feedback"
|
||||
style={{ display: "block" }}
|
||||
>
|
||||
{cpfError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Estado do CRM */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Estado do CRM: *</label>
|
||||
<select
|
||||
<select
|
||||
ref={crmUfRef}
|
||||
className="form-control form-control-custom"
|
||||
name="crm_uf"
|
||||
value={formData.crm_uf || ''}
|
||||
className="form-control form-control-custom"
|
||||
name="crm_uf"
|
||||
value={formData.crm_uf || ""}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="">Selecione</option>
|
||||
@ -404,29 +451,40 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* CRM */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">CRM: *</label>
|
||||
<input
|
||||
<input
|
||||
ref={crmRef}
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="crm"
|
||||
value={formData.crm || ''}
|
||||
onChange={handleChange}
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="crm"
|
||||
value={formData.crm || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Especialização */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Especialização:</label>
|
||||
<select className="form-control form-control-custom" name="specialty" value={formData.specialty || ''} onChange={handleChange}>
|
||||
<select
|
||||
className="form-control form-control-custom"
|
||||
name="specialty"
|
||||
value={formData.specialty || ""}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="">Selecione</option>
|
||||
<option value="Clínica Geral">Clínica médica (clínico geral)</option>
|
||||
<option value="Clínica Geral">
|
||||
Clínica médica (clínico geral)
|
||||
</option>
|
||||
<option value="Pediatria">Pediatria</option>
|
||||
<option value="Ginecologia">Ginecologia e obstetrícia</option>
|
||||
<option value="Cardiologia">Cardiologia</option>
|
||||
<option value="Ortopedia">Ortopedia e traumatologia</option>
|
||||
<option value="Oftalmologia">Oftalmologia</option>
|
||||
<option value="Otorrinolaringologia">Otorrinolaringologia</option>
|
||||
<option value="Otorrinolaringologia">
|
||||
Otorrinolaringologia
|
||||
</option>
|
||||
<option value="Dermatologia">Dermatologia</option>
|
||||
<option value="Neurologia">Neurologia</option>
|
||||
<option value="Psiquiatria">Psiquiatria</option>
|
||||
@ -441,39 +499,53 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
|
||||
{/* CONTATO */}
|
||||
<div className="form-section">
|
||||
<h4 className="section-header" onClick={() => handleToggleCollapse('contato')}>
|
||||
<h4
|
||||
className="section-header"
|
||||
onClick={() => handleToggleCollapse("contato")}
|
||||
>
|
||||
Contato
|
||||
<span className="section-toggle">
|
||||
{collapsedSections.contato ? '▲' : '▼'}
|
||||
{collapsedSections.contato ? "▲" : "▼"}
|
||||
</span>
|
||||
</h4>
|
||||
<div className={`collapse${collapsedSections.contato ? ' show' : ''}`}>
|
||||
<div
|
||||
className={`collapse${collapsedSections.contato ? " show" : ""}`}
|
||||
>
|
||||
<div className="row mt-3">
|
||||
{/* Email */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Email: *</label>
|
||||
<input
|
||||
<input
|
||||
ref={emailRef}
|
||||
type="email"
|
||||
className="form-control form-control-custom"
|
||||
name="email"
|
||||
value={formData.email || ''}
|
||||
onChange={handleChange}
|
||||
type="email"
|
||||
className="form-control form-control-custom"
|
||||
name="email"
|
||||
value={formData.email || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Telefone 1 (Principal) */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Telefone: *</label>
|
||||
<input
|
||||
<input
|
||||
ref={telefoneRef}
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="phone_mobile"
|
||||
value={formData.phone_mobile || ''}
|
||||
onChange={handleChange}
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="phone_mobile"
|
||||
value={formData.phone_mobile || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Telefone 2 (Opcional) */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Telefone 2:</label>
|
||||
<input type="text" className="form-control form-control-custom" name="phone2" value={formData.phone2 || ''} onChange={handleChange} />
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="phone2"
|
||||
value={formData.phone2 || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -481,60 +553,142 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
|
||||
{/* ENDEREÇO */}
|
||||
<div className="form-section">
|
||||
<h4 className="section-header" onClick={() => handleToggleCollapse('endereco')}>
|
||||
<h4
|
||||
className="section-header"
|
||||
onClick={() => handleToggleCollapse("endereco")}
|
||||
>
|
||||
Endereço
|
||||
<span className="section-toggle">
|
||||
{collapsedSections.endereco ? '▲' : '▼'}
|
||||
{collapsedSections.endereco ? "▲" : "▼"}
|
||||
</span>
|
||||
</h4>
|
||||
<div className={`collapse${collapsedSections.endereco ? ' show' : ''}`}>
|
||||
<div
|
||||
className={`collapse${collapsedSections.endereco ? " show" : ""}`}
|
||||
>
|
||||
<div className="row mt-3">
|
||||
{/* CEP */}
|
||||
<div className="col-md-4 mb-3">
|
||||
<label className="form-label">CEP:</label>
|
||||
<input type="text" className="form-control form-control-custom" name="cep" value={formData.cep || ''} onChange={handleChange} onBlur={handleCepBlur} />
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="cep"
|
||||
value={formData.cep || ""}
|
||||
onChange={handleChange}
|
||||
onBlur={handleCepBlur}
|
||||
/>
|
||||
</div>
|
||||
{/* Rua */}
|
||||
<div className="col-md-8 mb-3">
|
||||
<label className="form-label">Rua:</label>
|
||||
<input type="text" className="form-control form-control-custom" name="street" value={formData.street || ''} onChange={handleChange} />
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="street"
|
||||
value={formData.street || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Bairro */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Bairro:</label>
|
||||
<input type="text" className="form-control form-control-custom" name="neighborhood" value={formData.neighborhood || ''} onChange={handleChange} />
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="neighborhood"
|
||||
value={formData.neighborhood || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Cidade */}
|
||||
<div className="col-md-4 mb-3">
|
||||
<label className="form-label">Cidade:</label>
|
||||
<input type="text" className="form-control form-control-custom" name="city" value={formData.city || ''} onChange={handleChange} />
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="city"
|
||||
value={formData.city || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Estado */}
|
||||
<div className="col-md-2 mb-3">
|
||||
<label className="form-label">Estado:</label>
|
||||
<input type="text" className="form-control form-control-custom" name="state" value={formData.state || ''} onChange={handleChange} />
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="state"
|
||||
value={formData.state || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Número */}
|
||||
<div className="col-md-4 mb-3">
|
||||
<label className="form-label">Número:</label>
|
||||
<input type="text" className="form-control form-control-custom" name="number" value={formData.number || ''} onChange={handleChange} />
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="number"
|
||||
value={formData.number || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Complemento */}
|
||||
<div className="col-md-8 mb-3">
|
||||
<label className="form-label">Complemento:</label>
|
||||
<input type="text" className="form-control form-control-custom" name="complement" value={formData.complement || ''} onChange={handleChange} />
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-custom"
|
||||
name="complement"
|
||||
value={formData.complement || ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* BOTÕES DE AÇÃO */}
|
||||
{/* HORÁRIOS */}
|
||||
<div className="form-section">
|
||||
<h4
|
||||
className="section-header"
|
||||
onClick={() => handleToggleCollapse("horarios")}
|
||||
>
|
||||
Horários de Atendimento
|
||||
<span className="section-toggle">
|
||||
{collapsedSections.horarios ? "▲" : "▼"}
|
||||
</span>
|
||||
</h4>
|
||||
<div
|
||||
className={`collapse${collapsedSections.horarios ? " show" : ""}`}
|
||||
>
|
||||
<div className="row mt-3">
|
||||
<div className="col-12 mb-3">
|
||||
<p className="form-label text-muted">
|
||||
Defina seus horários de atendimento para cada dia da semana.
|
||||
Marque um dia para começar a adicionar blocos de tempo.
|
||||
</p>
|
||||
<HorariosDisponibilidade
|
||||
initialAvailability={formData.availability}
|
||||
onUpdate={handleAvailabilityUpdate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* BOTÕES DE AÇÃO */}
|
||||
<div className="actions-container">
|
||||
<button
|
||||
className="btn btn-success btn-submit"
|
||||
onClick={handleSubmit}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? 'Salvando...' : 'Salvar Médico'}
|
||||
{isLoading ? "Salvando..." : "Salvar Médico"}
|
||||
</button>
|
||||
|
||||
|
||||
<Link to={`/${location.pathname.split("/")[1]}/medicos`}>
|
||||
<button className="btn btn-light btn-cancel">
|
||||
Cancelar
|
||||
</button>
|
||||
<button className="btn btn-light btn-cancel">Cancelar</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@ -542,4 +696,4 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default DoctorForm;
|
||||
export default DoctorForm;
|
||||
|
||||
401
src/components/doctors/HorariosDisponibilidade.jsx
Normal file
401
src/components/doctors/HorariosDisponibilidade.jsx
Normal file
@ -0,0 +1,401 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { Clock } from "lucide-react";
|
||||
|
||||
const initialBlockTemplate = {
|
||||
id: null,
|
||||
inicio: "09:00",
|
||||
termino: "17:00",
|
||||
isNew: true,
|
||||
};
|
||||
|
||||
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: [] },
|
||||
];
|
||||
|
||||
const HorariosDisponibilidade = ({
|
||||
initialAvailability = emptyAvailabilityTemplate,
|
||||
onUpdate,
|
||||
}) => {
|
||||
const [availability, setAvailability] = useState(initialAvailability);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialAvailability !== emptyAvailabilityTemplate) {
|
||||
setAvailability(initialAvailability);
|
||||
}
|
||||
}, [initialAvailability]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onUpdate) {
|
||||
onUpdate(availability);
|
||||
}
|
||||
}, [availability, onUpdate]);
|
||||
|
||||
const handleDayCheck = useCallback((dayIndex, currentIsChecked) => {
|
||||
const isChecked = !currentIsChecked;
|
||||
|
||||
setAvailability((prev) =>
|
||||
prev.map((day, i) =>
|
||||
i === dayIndex
|
||||
? {
|
||||
...day,
|
||||
isChecked,
|
||||
blocos: isChecked
|
||||
? day.blocos.length === 0
|
||||
? [
|
||||
{
|
||||
...initialBlockTemplate,
|
||||
id: Date.now() + Math.random(),
|
||||
isNew: true,
|
||||
},
|
||||
]
|
||||
: day.blocos
|
||||
: [],
|
||||
}
|
||||
: day
|
||||
)
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleAddBlock = useCallback((dayIndex) => {
|
||||
const tempId = Date.now() + Math.random();
|
||||
const newBlock = { ...initialBlockTemplate, id: tempId, isNew: true };
|
||||
|
||||
setAvailability((prev) =>
|
||||
prev.map((day, i) =>
|
||||
i === dayIndex
|
||||
? {
|
||||
...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: "16px",
|
||||
marginBottom: "16px",
|
||||
borderRadius: "12px",
|
||||
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" : "32px",
|
||||
width: window.innerWidth < 640 ? "100%" : "auto",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
marginBottom: window.innerWidth < 640 ? "8px" : "0",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
htmlFor={`inicio-${dayIndex}-${bloco.id}`}
|
||||
style={{ fontWeight: 500, color: "#4b5563", width: "64px" }}
|
||||
>
|
||||
Início:
|
||||
</label>
|
||||
<div style={{ position: "relative" }}>
|
||||
<input
|
||||
id={`inicio-${dayIndex}-${bloco.id}`}
|
||||
type="time"
|
||||
value={bloco.inicio}
|
||||
onChange={(e) =>
|
||||
handleTimeChange(dayIndex, bloco.id, "inicio", e.target.value)
|
||||
}
|
||||
style={{
|
||||
padding: "8px",
|
||||
border: "1px solid #d1d5db",
|
||||
borderRadius: "8px",
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
outline: "none",
|
||||
}}
|
||||
step="300"
|
||||
/>
|
||||
<Clock
|
||||
size={16}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: "12px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: "#9ca3af",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<label
|
||||
htmlFor={`termino-${dayIndex}-${bloco.id}`}
|
||||
style={{ fontWeight: 500, color: "#4b5563", width: "64px" }}
|
||||
>
|
||||
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: "8px",
|
||||
border: "1px solid #d1d5db",
|
||||
borderRadius: "8px",
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
outline: "none",
|
||||
}}
|
||||
step="300"
|
||||
/>
|
||||
<Clock
|
||||
size={16}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: "12px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: "#9ca3af",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => handleRemoveBlock(dayIndex, bloco.id)}
|
||||
style={{
|
||||
marginTop: window.innerWidth < 640 ? "16px" : "0",
|
||||
padding: "8px 24px",
|
||||
backgroundColor: "#ef4444",
|
||||
color: "white",
|
||||
fontWeight: 600,
|
||||
borderRadius: "12px",
|
||||
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,
|
||||
}}
|
||||
>
|
||||
(Novo)
|
||||
</span>
|
||||
)}
|
||||
</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: "20px",
|
||||
borderRadius: "12px",
|
||||
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: "24px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "12px 24px",
|
||||
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;
|
||||
@ -14,6 +14,17 @@
|
||||
"icon": "hospital-fill",
|
||||
"url": "/admin/medicos"
|
||||
},
|
||||
{
|
||||
"name": "Agendamentos",
|
||||
"icon": "calendar-plus-fill",
|
||||
"url": "/admin/agendamento"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Relaototios",
|
||||
"icon": "table",
|
||||
"url": "/admin/laudo"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Gestão de Usuários",
|
||||
@ -25,12 +36,5 @@
|
||||
"name": "Painel Administrativo",
|
||||
"icon": "file-bar-graph-fill",
|
||||
"url": "/admin/painel"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Laudo do Paciente",
|
||||
"icon": "table",
|
||||
"url": "/admin/laudo"
|
||||
}
|
||||
|
||||
]
|
||||
@ -6,6 +6,7 @@ import TabelaAgendamentoDia from '../components/AgendarConsulta/TabelaAgendament
|
||||
import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana';
|
||||
import TabelaAgendamentoMes from '../components/AgendarConsulta/TabelaAgendamentoMes';
|
||||
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta';
|
||||
// Importação de endpoints para lógica da Fila de Espera e Médicos (versão main)
|
||||
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient.js';
|
||||
import { GetAllDoctors, GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor.js';
|
||||
|
||||
@ -13,7 +14,6 @@ import { useAuth } from '../components/utils/AuthProvider.js';
|
||||
// ✨ NOVO: Caminho de importação corrigido com base na sua estrutura de pastas
|
||||
import AgendamentosMes from '../components/AgendarConsulta/DadosConsultasMock.js';
|
||||
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import "./style/Agendamento.css";
|
||||
import './style/FilaEspera.css';
|
||||
@ -21,21 +21,22 @@ import { Search } from 'lucide-react';
|
||||
|
||||
|
||||
|
||||
const Agendamento = ({setDictInfo}) => {
|
||||
const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [selectedID, setSelectedId] = useState('0')
|
||||
const [filaEsperaData, setfilaEsperaData] = useState([])
|
||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||
// Estados mesclados
|
||||
const [selectedID, setSelectedId] = useState('0') // (main)
|
||||
const [filaEsperaData, setfilaEsperaData] = useState([]) // (main)
|
||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||
const [tabela, setTabela] = useState('diario');
|
||||
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [agendamentos, setAgendamentos] = useState()
|
||||
const [agendamentos, setAgendamentos] = useState() // (main)
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({})
|
||||
const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({})
|
||||
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState()
|
||||
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState() // (main)
|
||||
|
||||
const [ListaDeMedicos, setListaDeMedicos] = useState([])
|
||||
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([])
|
||||
@ -44,17 +45,20 @@ const Agendamento = ({setDictInfo}) => {
|
||||
|
||||
let authHeader = getAuthorizationHeader()
|
||||
|
||||
// Função FiltrarAgendamentos (Mesclado: Mantido o da MAIN, mais completo e com ordenação/fila de espera real)
|
||||
const FiltrarAgendamentos = async (listaTodosAgendamentos) => {
|
||||
const ConfigurarFiladeEspera = async (patient_id, doctor_id, agendamento) => {
|
||||
// Assumindo que GetDoctorByID e GetPatientByID estão definidos no seu escopo
|
||||
let medico = await GetDoctorByID(doctor_id, authHeader);
|
||||
let paciente = await GetPatientByID(patient_id, authHeader);
|
||||
|
||||
console.log(medico)
|
||||
|
||||
let dicionario = {
|
||||
agendamento: agendamento,
|
||||
Infos: {
|
||||
nome_nedico: medico.full_name,
|
||||
doctor_id: medico.id,
|
||||
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
|
||||
@ -66,11 +70,9 @@ const Agendamento = ({setDictInfo}) => {
|
||||
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 {
|
||||
@ -84,42 +86,30 @@ const Agendamento = ({setDictInfo}) => {
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 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
|
||||
setAgendamentosOrganizados(DictAgendamentosFinal);
|
||||
setfilaEsperaData(ListaFilaDeEspera);
|
||||
};
|
||||
|
||||
// Requisição inicial para mostrar os agendamentos do banco de dados
|
||||
useEffect(() => {
|
||||
var myHeaders = new Headers();
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("apikey", API_KEY)
|
||||
|
||||
@ -131,7 +121,7 @@ const Agendamento = ({setDictInfo}) => {
|
||||
|
||||
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);})
|
||||
.then(result => {FiltrarAgendamentos(result);console.log(result)})
|
||||
.catch(error => console.log('error', error));
|
||||
|
||||
const PegarTodosOsMedicos = async () => {
|
||||
@ -145,7 +135,6 @@ const Agendamento = ({setDictInfo}) => {
|
||||
PegarTodosOsMedicos()
|
||||
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
console.log("mudou FiltredTodosMedicos:", FiltredTodosMedicos);
|
||||
if (FiltredTodosMedicos.length === 1) {
|
||||
@ -183,21 +172,10 @@ fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${sel
|
||||
.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 =>
|
||||
@ -209,7 +187,6 @@ const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
||||
|
||||
|
||||
|
||||
// Lógica para filtrar os dados da AGENDA (AgendamentosMes)
|
||||
const filteredAgendamentos = useMemo(() => {
|
||||
if (!searchTerm.trim()) {
|
||||
return AgendamentosMes;
|
||||
@ -249,6 +226,7 @@ const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
||||
return ListaDiasDatas
|
||||
}
|
||||
|
||||
|
||||
const handleClickAgendamento = (agendamento) => {
|
||||
if (agendamento.status !== 'vazio') return
|
||||
else setPageConsulta(true)
|
||||
@ -262,7 +240,6 @@ const handleSearchMedicos = (term) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Lógica simples de filtragem:
|
||||
const filtered = ListaDeMedicos.filter(medico =>
|
||||
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
||||
);
|
||||
@ -275,65 +252,65 @@ const handleSearchMedicos = (term) => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Agendar nova consulta</h1>
|
||||
|
||||
<button >
|
||||
Ir para Formulário de Disponibilidade
|
||||
</button>
|
||||
|
||||
<button onClick={() => setPageConsulta(true)}>
|
||||
Adcionar consulta
|
||||
</button>
|
||||
|
||||
|
||||
<div className="btns-gerenciamento-e-consulta" style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
|
||||
<button className="btn btn-primary" onClick={() => setPageConsulta(true)}>
|
||||
<i className="bi bi-plus-circle"></i> Adicionar Consulta
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="manage-button btn"
|
||||
onClick={() => navigate("/secretaria/excecoes-disponibilidade")}
|
||||
>
|
||||
<i className="bi bi-gear-fill me-1"></i>
|
||||
Gerenciar Exceções
|
||||
</button>
|
||||
|
||||
<button className='manage-button btn' onClick={() => navigate('/secretaria/disponibilidade')}>
|
||||
<i className="bi bi-gear-fill me-1"></i>
|
||||
Mudar Disponibilidade
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!PageNovaConsulta ? (
|
||||
<div className='atendimento-eprocura'>
|
||||
<div className='unidade-selecionarprofissional'>
|
||||
|
||||
{/* Bloco de busca por médico */}
|
||||
<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 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)}
|
||||
/>
|
||||
</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>
|
||||
|
||||
{/* 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'>
|
||||
@ -382,6 +359,7 @@ const handleSearchMedicos = (term) => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Componentes de Tabela - Adicionado props de delete da main */}
|
||||
{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} />}
|
||||
@ -404,11 +382,10 @@ const handleSearchMedicos = (term) => {
|
||||
<table className="fila-tabela">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Telefone</th>
|
||||
|
||||
<th>Telefone</th>
|
||||
<th>Entrou na fila de espera</th>
|
||||
<th>Nome do Paciente</th> {/* Ajustado o cabeçalho */}
|
||||
<th>CPF</th> {/* Ajustado o cabeçalho */}
|
||||
<th>Médico Solicitado</th> {/* Ajustado o cabeçalho */}
|
||||
<th>Data da Solicitação</th> {/* Ajustado o cabeçalho */}
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -416,15 +393,15 @@ const handleSearchMedicos = (term) => {
|
||||
{filaEsperaData.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td> <p>{item.Infos?.paciente_nome} </p> </td>
|
||||
<td><p>{} </p></td>
|
||||
<td>{}</td>
|
||||
<td>{}</td>
|
||||
<td><p>{item.Infos?.paciente_cpf} </p></td>
|
||||
<td><p>{item.Infos?.nome_medico} </p></td>
|
||||
<td>{dayjs(item.agendamento.created_at).format('DD/MM/YYYY HH:mm')}</td>
|
||||
<td> <div className="d-flex gap-2">
|
||||
|
||||
<button className="btn btn-sm btn-edit"
|
||||
onClick={() => {
|
||||
console.log(item, 'item')
|
||||
navigate(`${2}/edit`)
|
||||
navigate(`${2}/edit`)
|
||||
setDictInfo(item)
|
||||
}}
|
||||
>
|
||||
@ -452,9 +429,10 @@ const handleSearchMedicos = (term) => {
|
||||
</section>
|
||||
</div>
|
||||
) : (
|
||||
<AgendamentoCadastroManager />
|
||||
<AgendamentoCadastroManager setPageConsulta={setPageConsulta} />
|
||||
)}
|
||||
|
||||
{/* Modal de Confirmação de Exclusão (Da MAIN) */}
|
||||
{showDeleteModal && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
|
||||
@ -5,7 +5,7 @@ import { useAuth } from '../components/utils/AuthProvider'
|
||||
import { useEffect,useState } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import { UserInfos } from '../components/utils/Functions-Endpoints/General'
|
||||
const AgendamentoCadastroManager = () => {
|
||||
const AgendamentoCadastroManager = ({setPageConsulta}) => {
|
||||
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
const [agendamento, setAgendamento] = useState({status:'confirmed'})
|
||||
@ -66,7 +66,7 @@ const AgendamentoCadastroManager = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
<FormNovaConsulta onSave={handleSave} agendamento={agendamento} setAgendamento={setAgendamento}/>
|
||||
<FormNovaConsulta onSave={handleSave} agendamento={agendamento} setAgendamento={setAgendamento} onCancel={() => setPageConsulta(false)}/>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -17,6 +17,8 @@ const AgendamentoEditPage = ({setDictInfo, DictInfo}) => {
|
||||
|
||||
let id = params.id
|
||||
|
||||
console.log(DictInfo, "DENTRO DO EDITAR")
|
||||
|
||||
//console.log(DictInfo, 'aqui')
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
145
src/pages/DisponibilidadesDoctorPage.jsx
Normal file
145
src/pages/DisponibilidadesDoctorPage.jsx
Normal file
@ -0,0 +1,145 @@
|
||||
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 DisponibilidadesDoctorPage = () => {
|
||||
const [disponibilidades, setDisponibilidades] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [filtroMedicoId, setFiltroMedicoId] = useState("");
|
||||
const [filtroActive, setFiltroActive] = useState("true");
|
||||
const [medicoValido, setMedicoValido] = useState(false);
|
||||
|
||||
const fetchDisponibilidades = useCallback(async (doctorId, activeStatus) => {
|
||||
setLoading(true);
|
||||
let url = `${ENDPOINT_LISTAR}?select=*`;
|
||||
if (doctorId) url += `&doctor_id=eq.${doctorId}`;
|
||||
if (activeStatus === "true" || activeStatus === "false") url += `&active=eq.${activeStatus}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const result = await response.json();
|
||||
setDisponibilidades(Array.isArray(result) ? result : []);
|
||||
setMedicoValido(Array.isArray(result) && result.length > 0);
|
||||
} catch (error) {
|
||||
setDisponibilidades([]);
|
||||
setMedicoValido(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (filtroMedicoId) {
|
||||
fetchDisponibilidades(filtroMedicoId, filtroActive);
|
||||
} else {
|
||||
setDisponibilidades([]);
|
||||
setMedicoValido(false);
|
||||
}
|
||||
}, [filtroMedicoId, filtroActive, fetchDisponibilidades]);
|
||||
|
||||
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={medicoValido ? `../medicos/${filtroMedicoId}/edit` : "#"}
|
||||
className="btn-primary"
|
||||
style={{
|
||||
padding: "10px 20px",
|
||||
fontSize: "14px",
|
||||
whiteSpace: "nowrap",
|
||||
textDecoration: "none",
|
||||
display: "inline-block",
|
||||
opacity: medicoValido ? 1 : 0.6,
|
||||
pointerEvents: medicoValido ? "auto" : "none",
|
||||
}}
|
||||
>
|
||||
+ Gerenciar Disponibilidades
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="atendimento-eprocura">
|
||||
<div className="busca-atendimento">
|
||||
<div>
|
||||
<i className="fa-solid fa-user-doctor"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filtrar por ID do Médico..."
|
||||
value={filtroMedicoId}
|
||||
onChange={(e) => setFiltroMedicoId(e.target.value)}
|
||||
style={{ border: "1px solid #ccc", borderRadius: "4px", padding: "5px" }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<i className="fa-solid fa-check-circle"></i>
|
||||
<select
|
||||
value={filtroActive}
|
||||
onChange={(e) => setFiltroActive(e.target.value)}
|
||||
style={{ padding: "8px", border: "1px solid #ccc", borderRadius: "4px" }}
|
||||
>
|
||||
<option value="true">Ativas</option>
|
||||
<option value="false">Inativas</option>
|
||||
<option value="">Todas</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="calendario-ou-filaespera">
|
||||
<div className="fila-container">
|
||||
<h2 className="fila-titulo">
|
||||
Disponibilidades Encontradas ({disponibilidades.length})
|
||||
</h2>
|
||||
|
||||
{loading ? (
|
||||
<p className="text-center py-10">Carregando disponibilidades...</p>
|
||||
) : disponibilidades.length === 0 ? (
|
||||
<p className="text-center py-10">
|
||||
Nenhuma disponibilidade encontrada para os filtros aplicados.
|
||||
</p>
|
||||
) : (
|
||||
<table className="fila-tabela" style={{ width: "100%", borderCollapse: "collapse" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
{["ID", "ID Médico", "Dia da Semana", "Início", "Término", "Intervalo (min)", "Tipo Consulta", "Status"].map(
|
||||
(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" }}>{disp.id || "N/A"}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.doctor_id}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.weekday}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.start_time || "N/A"}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.end_time || "N/A"}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.slot_minutes}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.appointment_type}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>
|
||||
<span className={`status-tag ${disp.active ? "legenda-item-realizado" : "legenda-item-cancelado"}`}>
|
||||
{disp.active ? "Ativa" : "Inativa"}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DisponibilidadesDoctorPage;
|
||||
@ -21,10 +21,11 @@ function DoctorCadastroManager() {
|
||||
const authHeader = getAuthorizationHeader();
|
||||
|
||||
try {
|
||||
console.log(authHeader)
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
myHeaders.append("apikey", API_KEY);
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("Authorization",`Bearer ${authHeader.split(` `)[1]}` );
|
||||
|
||||
console.log('Dados recebidos do Form:', doctorData);
|
||||
|
||||
@ -58,7 +59,7 @@ function DoctorCadastroManager() {
|
||||
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("Response ok:", response.ok);
|
||||
|
||||
@ -1,77 +1,146 @@
|
||||
import React from 'react'
|
||||
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor'
|
||||
import DoctorForm from '../components/doctors/DoctorForm'
|
||||
import { useAuth } from '../components/utils/AuthProvider'
|
||||
import {useEffect, useState} from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import API_KEY from '../components/utils/apiKeys'
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import { useParams, useSearchParams } from "react-router-dom";
|
||||
import { GetDoctorByID } from "../components/utils/Functions-Endpoints/Doctor";
|
||||
import DoctorForm from "../components/doctors/DoctorForm";
|
||||
import { useAuth } from "../components/utils/AuthProvider";
|
||||
import API_KEY from "../components/utils/apiKeys";
|
||||
|
||||
const ENDPOINT_AVAILABILITY =
|
||||
"https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_availability";
|
||||
|
||||
const DoctorEditPage = () => {
|
||||
const {getAuthorizationHeader, isAuthenticated} = useAuth();
|
||||
const [DoctorToPUT, setDoctorPUT] = useState({})
|
||||
|
||||
const Parametros = useParams()
|
||||
const { getAuthorizationHeader } = useAuth();
|
||||
const [DoctorToPUT, setDoctorPUT] = useState({});
|
||||
|
||||
const DoctorID = Parametros.id
|
||||
const Parametros = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const DoctorID = Parametros.id;
|
||||
const availabilityId = searchParams.get("availabilityId");
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const authHeader = getAuthorizationHeader()
|
||||
const [availabilityToPATCH, setAvailabilityToPATCH] = useState(null);
|
||||
const [mode, setMode] = useState("doctor");
|
||||
|
||||
GetDoctorByID(DoctorID, authHeader)
|
||||
.then((data) => {
|
||||
console.log(data, "médico vindo da API");
|
||||
setDoctorPUT(data[0])
|
||||
; // supabase retorna array
|
||||
useEffect(() => {
|
||||
const authHeader = getAuthorizationHeader();
|
||||
|
||||
if (availabilityId) {
|
||||
setMode("availability");
|
||||
|
||||
fetch(`${ENDPOINT_AVAILABILITY}?id=eq.${availabilityId}&select=*`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
apikey: API_KEY,
|
||||
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 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();
|
||||
myHeaders.append("apikey", API_KEY);
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
|
||||
var raw = JSON.stringify(DoctorToPUT);
|
||||
var raw = JSON.stringify(DoctorToPUT);
|
||||
|
||||
console.log("Enviando médico para atualização:", DoctorToPUT);
|
||||
console.log("Enviando médico para atualização (PUT):", DoctorToPUT);
|
||||
|
||||
var requestOptions = {
|
||||
method: 'PUT',
|
||||
headers: myHeaders,
|
||||
body: raw,
|
||||
redirect: 'follow'
|
||||
var requestOptions = {
|
||||
method: "PUT",
|
||||
headers: myHeaders,
|
||||
body: raw,
|
||||
redirect: "follow",
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`,
|
||||
requestOptions
|
||||
);
|
||||
console.log("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 {
|
||||
const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`,requestOptions);
|
||||
console.log(response)
|
||||
|
||||
} catch (error) {
|
||||
console.error("Erro ao atualizar paciente:", error);
|
||||
throw error;
|
||||
}
|
||||
// 2. Função para Atualizar DISPONIBILIDADE (PATCH)
|
||||
const HandlePatchAvailability = async (data) => {
|
||||
const authHeader = getAuthorizationHeader();
|
||||
|
||||
}
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("apikey", API_KEY);
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
|
||||
var raw = JSON.stringify(data);
|
||||
|
||||
console.log("Enviando disponibilidade para atualização (PATCH):", data);
|
||||
|
||||
var requestOptions = {
|
||||
method: "PATCH",
|
||||
headers: myHeaders,
|
||||
body: raw,
|
||||
redirect: "follow",
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${ENDPOINT_AVAILABILITY}?id=eq.${availabilityId}`,
|
||||
requestOptions
|
||||
);
|
||||
console.log("Resposta PATCH Disponibilidade:", response);
|
||||
alert("Disponibilidade atualizada com sucesso!");
|
||||
// Opcional: Redirecionar de volta para a lista de disponibilidades
|
||||
// navigate('/disponibilidades');
|
||||
} catch (error) {
|
||||
console.error("Erro ao atualizar disponibilidade:", error);
|
||||
alert("Erro ao atualizar disponibilidade.");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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
|
||||
onSave={HandlePutDoctor}
|
||||
|
||||
formData={DoctorToPUT}
|
||||
setFormData={setDoctorPUT}
|
||||
|
||||
/>
|
||||
|
||||
<DoctorForm
|
||||
onSave={
|
||||
mode === "availability" ? HandlePatchAvailability : HandlePutDoctor
|
||||
}
|
||||
formData={mode === "availability" ? availabilityToPATCH : DoctorToPUT}
|
||||
setFormData={
|
||||
mode === "availability" ? setAvailabilityToPATCH : setDoctorPUT
|
||||
}
|
||||
isEditingAvailability={mode === "availability"}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default DoctorEditPage
|
||||
export default DoctorEditPage;
|
||||
|
||||
214
src/pages/ExcecoesDisponibilidade.jsx
Normal file
214
src/pages/ExcecoesDisponibilidade.jsx
Normal file
@ -0,0 +1,214 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import FormCriarExcecao from '../components/FormCriarExcecao';
|
||||
import "../components/AgendarConsulta/style/formagendamentos.css";
|
||||
import "./style/Agendamento.css";
|
||||
import './style/FilaEspera.css';
|
||||
|
||||
const ENDPOINT_LISTAR = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_exceptions";
|
||||
const ENDPOINT_DELETAR = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_exceptions/";
|
||||
|
||||
const ExcecoesDisponibilidade = () => {
|
||||
|
||||
const [pageNovaExcecao, setPageNovaExcecao] = useState(false);
|
||||
const [excecoes, setExcecoes] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Filtros
|
||||
const [filtroMedicoId, setFiltroMedicoId] = useState('');
|
||||
const [filtroData, setFiltroData] = useState(dayjs().format('YYYY-MM-DD'));
|
||||
|
||||
// Estado para controlar a visualização (Diário, Semanal)
|
||||
const [visualizacao, setVisualizacao] = useState('diario');
|
||||
|
||||
// Função para buscar as exceções
|
||||
const fetchExcecoes = useCallback(async (doctorId, date) => {
|
||||
setLoading(true);
|
||||
let url = `${ENDPOINT_LISTAR}?select=*`;
|
||||
|
||||
if (doctorId) {
|
||||
url += `&doctor_id=eq.${doctorId}`; // Assume filtro por igualdade de ID
|
||||
}
|
||||
if (date) {
|
||||
url += `&date=eq.${date}`; // Assume filtro por igualdade de data
|
||||
}
|
||||
|
||||
try {
|
||||
const requestOptions = { method: 'GET', redirect: 'follow' };
|
||||
const response = await fetch(url, requestOptions);
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && Array.isArray(result)) {
|
||||
setExcecoes(result);
|
||||
} else {
|
||||
setExcecoes([]);
|
||||
console.error("Erro ao listar exceções:", result);
|
||||
alert("Erro ao carregar lista de exceções.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro na requisição de listagem de exceções:', error);
|
||||
setExcecoes([]);
|
||||
alert("Erro de comunicação com o servidor ao listar exceções.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Função para deletar uma exceção
|
||||
const deletarExcecao = async (id) => {
|
||||
if (!window.confirm(`Tem certeza que deseja deletar a exceção com ID: ${id}?`)) return;
|
||||
|
||||
try {
|
||||
const requestOptions = { method: 'DELETE', redirect: 'follow' };
|
||||
const response = await fetch(`${ENDPOINT_DELETAR}${id}`, requestOptions);
|
||||
|
||||
if (response.ok || response.status === 204) {
|
||||
alert(`Exceção ${id} deletada com sucesso.`);
|
||||
fetchExcecoes(filtroMedicoId, filtroData); // Recarrega a lista
|
||||
} else {
|
||||
const result = await response.json();
|
||||
alert(`Erro ao deletar exceção. Detalhes: ${result.message || JSON.stringify(result)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro na requisição de deleção:', error);
|
||||
alert('Erro de comunicação ao tentar deletar a exceção.');
|
||||
}
|
||||
};
|
||||
|
||||
// Efeito para carregar exceções quando os filtros mudam
|
||||
useEffect(() => {
|
||||
fetchExcecoes(filtroMedicoId, filtroData);
|
||||
}, [fetchExcecoes, filtroMedicoId, filtroData]);
|
||||
|
||||
// Handler de cancelamento do formulário de criação
|
||||
const handleCancelForm = (recarregar = false) => {
|
||||
setPageNovaExcecao(false);
|
||||
if (recarregar) {
|
||||
fetchExcecoes(filtroMedicoId, filtroData); // Recarrega se a criação foi bem-sucedida
|
||||
}
|
||||
}
|
||||
|
||||
// Se o formulário de criação estiver aberto, renderiza apenas ele
|
||||
if (pageNovaExcecao) {
|
||||
return <FormCriarExcecao onCancel={handleCancelForm} doctorID={filtroMedicoId} />;
|
||||
}
|
||||
|
||||
// Renderiza a tela de listagem (layout da agenda)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
|
||||
<h1>Gerenciar Exceções de Disponibilidade</h1>
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={() => setPageNovaExcecao(true)}
|
||||
// Use a classe btn-primary que deve estar funcionando
|
||||
style={{ padding: '10px 20px', fontSize: '14px', whiteSpace: 'nowrap' }}
|
||||
>
|
||||
+ Criar Nova Exceção
|
||||
</button>
|
||||
</div>
|
||||
<div className='atendimento-eprocura'>
|
||||
|
||||
{/* Filtros e Busca (Adaptados do Agendamento) */}
|
||||
<div className='busca-atendimento'>
|
||||
<div>
|
||||
<i className="fa-solid fa-user-doctor"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filtrar por ID do Médico..."
|
||||
value={filtroMedicoId}
|
||||
onChange={(e) => setFiltroMedicoId(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<i className="fa-solid fa-calendar"></i>
|
||||
<input
|
||||
type="date"
|
||||
value={filtroData}
|
||||
onChange={(e) => setFiltroData(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Botões de Visualização (Dia/Semana/Mês) - Adaptados */}
|
||||
<div className='container-btns-agenda-fila_esepera'>
|
||||
<button
|
||||
className={`btn-agenda ${visualizacao === "diario" ? "opc-agenda-ativo" : ""}`}
|
||||
onClick={() => setVisualizacao('diario')}
|
||||
>
|
||||
Dia
|
||||
</button>
|
||||
<button
|
||||
className={`btn-fila-espera ${visualizacao === "semanal" ? "opc-filaespera-ativo" : ""}`}
|
||||
onClick={() => setVisualizacao('semanal')}
|
||||
>
|
||||
Semana
|
||||
</button>
|
||||
<button
|
||||
className={`btn-fila-espera ${visualizacao === "mensal" ? "opc-filaespera-ativo" : ""}`}
|
||||
onClick={() => setVisualizacao('mensal')}
|
||||
>
|
||||
Mês
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tabela de Exceções (Simulando a Tabela de Agendamentos) */}
|
||||
<section className='calendario-ou-filaespera'>
|
||||
<div className="fila-container">
|
||||
<h2 className="fila-titulo">Exceções em {filtroData} ({excecoes.length})</h2>
|
||||
{loading ? (
|
||||
<p>Carregando exceções...</p>
|
||||
) : excecoes.length === 0 ? (
|
||||
<p>Nenhuma exceção encontrada para os filtros aplicados.</p>
|
||||
) : (
|
||||
<table className="fila-tabela">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID Exceção</th>
|
||||
<th>ID Médico</th>
|
||||
<th>Data</th>
|
||||
<th>Início</th>
|
||||
<th>Término</th>
|
||||
<th>Tipo</th>
|
||||
<th>Motivo</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{excecoes.map((excecao, index) => (
|
||||
<tr key={excecao.id || index}>
|
||||
<td>{excecao.id || 'N/A'}</td>
|
||||
<td>{excecao.doctor_id}</td>
|
||||
<td>{excecao.date}</td>
|
||||
<td>{excecao.start_time ? excecao.start_time.substring(0, 5) : 'Dia Todo'}</td>
|
||||
<td>{excecao.end_time ? excecao.end_time.substring(0, 5) : 'Dia Todo'}</td>
|
||||
<td>
|
||||
<span className={`status-tag ${excecao.kind === 'bloqueio' ? 'legenda-item-cancelado' : 'legenda-item-realizado'}`}>
|
||||
{excecao.kind}
|
||||
</span>
|
||||
</td>
|
||||
<td>{excecao.reason}</td>
|
||||
<td>
|
||||
{excecao.id && (
|
||||
<button
|
||||
onClick={() => deletarExcecao(excecao.id)}
|
||||
style={{ background: '#dc3545', color: 'white', border: 'none', padding: '5px 10px', cursor: 'pointer', borderRadius: '4px' }}
|
||||
>
|
||||
Deletar
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExcecoesDisponibilidade;
|
||||
@ -72,7 +72,7 @@ function PatientCadastroManager({ setCurrentPage }) {
|
||||
|
||||
const cleanedData = {
|
||||
full_name: patientData.full_name,
|
||||
cpf: cpfLimpo,
|
||||
cpf: patientData.cpf,
|
||||
email: patientData.email,
|
||||
phone_mobile: patientData.phone_mobile,
|
||||
birth_date: patientData.birth_date || null,
|
||||
|
||||
@ -14,6 +14,8 @@ import DoctorEditPage from "../../pages/DoctorEditPage";
|
||||
import UserDashboard from '../../PagesAdm/gestao.jsx';
|
||||
import PainelAdministrativo from '../../PagesAdm/painel.jsx';
|
||||
import admItems from "../../data/sidebar-items-adm.json";
|
||||
|
||||
|
||||
// ...restante do código...
|
||||
function Perfiladm() {
|
||||
return (
|
||||
|
||||
@ -4,7 +4,7 @@ import Sidebar from "../../components/Sidebar";
|
||||
import DoctorRelatorioManager from "../../PagesMedico/DoctorRelatorioManager";
|
||||
import Prontuario from "../../PagesMedico/prontuario";
|
||||
import Relatorio from "../../PagesMedico/relatorio";
|
||||
import Agendamento from "../../PagesMedico/Agendamento";
|
||||
import DoctorAgendamentoManager from "../../PagesMedico/DoctorAgendamentoManager";
|
||||
import Chat from "../../PagesMedico/Chat";
|
||||
import DoctorItems from "../../data/sidebar-items-medico.json";
|
||||
import FormNovoRelatorio from "../../PagesMedico/FormNovoRelatorio";
|
||||
@ -23,7 +23,7 @@ function PerfilMedico() {
|
||||
<Route path="/relatorios/:id/edit" element={<EditPageRelatorio />} />
|
||||
<Route path="/prontuario" element={<Prontuario />} />
|
||||
<Route path="/relatorios" element={<DoctorRelatorioManager />} />
|
||||
<Route path="/agendamentoMedico" element={<Agendamento />} />
|
||||
<Route path="/agendamentoMedico" element={<DoctorAgendamentoManager />} />
|
||||
<Route path="/chat" element={<Chat />} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import Sidebar from "../../components/Sidebar";
|
||||
import PacienteItems from "../../data/sidebar-items-paciente.json";
|
||||
import Agendamento from "../../pages/Agendamento";
|
||||
|
||||
import LaudoManager from "../../pages/LaudoManager";
|
||||
import ConsultaCadastroManager from "../../PagesPaciente/ConsultaCadastroManager";
|
||||
import ConsultasPaciente from "../../PagesPaciente/ConsultasPaciente";
|
||||
|
||||
function PerfilPaciente({ onLogout }) {
|
||||
return (
|
||||
<div id="app" className="active">
|
||||
@ -11,7 +14,8 @@ function PerfilPaciente({ onLogout }) {
|
||||
<div id="main">
|
||||
<Routes>
|
||||
<Route path="/" element={<LaudoManager />} />
|
||||
<Route path="agendamento" element={<Agendamento />} />
|
||||
<Route path="agendamento" element={<ConsultasPaciente />} />
|
||||
<Route path="agendamento/criar" element={<ConsultaCadastroManager />} />
|
||||
<Route path="laudo" element={<LaudoManager />} />
|
||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||
</Routes>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
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 Inicio from "../../pages/Inicio";
|
||||
import TablePaciente from "../../pages/TablePaciente";
|
||||
@ -15,7 +15,8 @@ import Details from "../../pages/Details";
|
||||
import EditPage from "../../pages/EditPage";
|
||||
import DoctorDetails from "../../pages/DoctorDetails";
|
||||
import DoctorEditPage from "../../pages/DoctorEditPage";
|
||||
import FormDisponibilidade from "../../components/AgendarConsulta/FormDisponibilidade";
|
||||
import ExcecoesDisponibilidade from "../../pages/ExcecoesDisponibilidade";
|
||||
import DisponibilidadesDoctorPage from "../../pages/DisponibilidadesDoctorPage"
|
||||
import AgendamentoEditPage from "../../pages/AgendamentoEditPage";
|
||||
|
||||
function PerfilSecretaria({ onLogout }) {
|
||||
@ -37,9 +38,11 @@ function PerfilSecretaria({ onLogout }) {
|
||||
<Route path="medicos/:id/edit" element={<DoctorEditPage />} />
|
||||
<Route path="agendamento" element={<Agendamento setDictInfo={setDictInfo}/>} />
|
||||
<Route path="agendamento/:id/edit" element={<AgendamentoEditPage setDictInfo={setDictInfo} DictInfo={DictInfo}/>} />
|
||||
<Route path="laudo" element={<LaudoManager />} />
|
||||
<Route path="laudo" element={<LaudoManager />} />
|
||||
<Route path="disponibilidade" element={<DisponibilidadesDoctorPage />} />
|
||||
<Route path="horarios" element={<HorariosDisponibilidade/>}/>
|
||||
<Route path="excecoes-disponibilidade" element={<ExcecoesDisponibilidade />} />
|
||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||
<Route path="form-disponibilidade" element={<FormDisponibilidade />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user