Adições finais

This commit is contained in:
Eduarda-SS 2025-12-04 13:14:16 -03:00
parent 0b47a96589
commit 45ac442e4f
13 changed files with 349 additions and 17 deletions

View File

@ -12,7 +12,7 @@ import PerfilSecretaria from "./perfis/perfil_secretaria/PerfilSecretaria.jsx"
import PerfilFinanceiro from "./perfis/perfil_financeiro/PerfilFinanceiro.jsx";
import Perfiladm from "./perfis/Perfil_adm/PerfilAdmin.jsx";
import PerfilMedico from "./perfis/Perfil_medico/PerfilMedico.jsx";
import PerfilPaciente from "./perfis/Perfil_paciente/PerfilPaciente.jsx"
import PerfilPaciente from "./perfis/Perfil_paciente/Perfilpaciente.jsx"
// COMBINADO: Importações de ambas as versões
import ProfilePage from "./pages/geral/PerfilUsuario.jsx";

View File

@ -335,7 +335,7 @@ const FormCriarExcecao = ({ onCancel, doctorID }) => {
<label>Tipo de exceção *</label>
<select name="tipoAtendimento" onChange={handleAtendimentoChange} value={dadosAtendimento.tipoAtendimento} required>
<option value="" disabled>Selecione o tipo de exceção</option>
<option value="disponibilidade_extra" >Liberação</option>
<option value="disponibilidade_extra" >Disponibilidade Extra</option>
<option value="bloqueio" >Bloqueio</option>
</select>
</div>

View File

@ -0,0 +1,335 @@
import { useState, useEffect } from "react";
import { useAuth } from "../../_assets/utils/AuthProvider";
import API_KEY from "../../_assets/utils/apiKeys";
import { GetDoctorByName } from '../../_assets/utils/Functions-Endpoints/Doctor';
import "../../_assets/css/components/agendamento/FormAgendamento.css";
const ENDPOINT_CRIAR_EXCECAO = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_exceptions";
const FormCriarExcecao = ({ onCancel, doctorID }) => {
const { getAuthorizationHeader, user, getUserInfo } = useAuth();
// mantemos apenas os campos realmente necessários no formulário;
// profissional (id) e created_by serão preenchidos automaticamente a partir do usuário logado
const [dadosAtendimento, setDadosAtendimento] = useState({
profissional: doctorID || '',
tipoAtendimento: '',
dataAtendimento: '',
inicio: '',
termino: '',
motivo: ''
});
const handleAtendimentoChange = (e) => {
const { value, name } = e.target;
setDadosAtendimento(prev => ({
...prev,
[name]: value
}));
};
// preencher automaticamente o id do profissional (doctor_id) vindo do user/prop
useEffect(() => {
const resolvedDoctorId = doctorID || user?.doctor_id || user?.id || '';
setDadosAtendimento(prev => ({
...prev,
profissional: prev.profissional || resolvedDoctorId
}));
}, [doctorID, user]);
const ALLOWED_KINDS = ['disponibilidade_extra', 'bloqueio'];
// helper: normalize and validate Authorization header -> "Bearer <jwt>"
const resolveAuthHeader = () => {
try {
const maybe = getAuthorizationHeader ? getAuthorizationHeader() : null;
console.log('[DEBUG] getAuthorizationHeader() ->', maybe);
let raw = null;
if (!maybe) {
// fallback localStorage
try {
const stored = localStorage.getItem('user') || localStorage.getItem('auth') || null;
if (stored) {
const parsed = JSON.parse(stored);
raw = parsed?.access_token || parsed?.token || parsed?.auth?.access_token || null;
}
} catch (e) { console.warn('[DEBUG] parse localStorage failed', e); }
} else if (typeof maybe === 'string') {
raw = maybe;
} else if (typeof maybe === 'object') {
raw = maybe.Authorization || maybe.authorization || maybe.access_token || maybe.token || null;
}
if (!raw) return null;
// remove any leading "bearer " (case-insensitive) and possible duplicated prefixes
raw = String(raw).trim().replace(/^(?:bearer\s*)+/i, '');
console.log('[DEBUG] resolved raw token ->', raw);
// basic JWT structure check
if (typeof raw !== 'string' || raw.split('.').length !== 3) {
console.error('[DEBUG] token not a JWT:', raw);
return null;
}
return `Bearer ${raw}`;
} catch (err) {
console.warn('resolveAuthHeader error', err);
return null;
}
};
const getUidFromAuthHeader = (authHeader) => {
try {
if (!authHeader) return null;
const token = String(authHeader).replace(/Bearer\s+/i, '').trim();
if (!token || token.split('.').length !== 3) return null;
// decode base64url payload
const payloadBase64 = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
const padded = payloadBase64 + '='.repeat((4 - (payloadBase64.length % 4)) % 4);
const json = atob(padded);
const payload = JSON.parse(json);
return payload.sub || payload.user_id || payload.uid || payload.id || null;
} catch (err) {
console.warn('Não foi possível decodificar JWT para obter uid:', err);
return null;
}
};
const handleSubmitExcecao = async (e) => {
e.preventDefault();
console.log('[DEBUG] submit dadosAtendimento ->', dadosAtendimento);
const authHeader = resolveAuthHeader();
console.log('[DEBUG] authHeader to send ->', authHeader);
if (!authHeader) {
alert('Sessão inválida ou token ausente / inválido. Faça logout e login novamente.');
return;
}
let { profissional, dataAtendimento, tipoAtendimento, inicio, termino, motivo } = dadosAtendimento;
// tenta resolver profissional automaticamente se ainda não definido
profissional = profissional || doctorID || user?.doctor_id || user?.id || null;
// extrai uid do token (se policy do Supabase exigir auth.uid())
const authUid = getUidFromAuthHeader(authHeader) || user?.id || null;
// se a policy exige que doctor_id seja igual ao auth.uid(), force o valor aqui
if (authUid && !profissional) {
profissional = authUid;
setDadosAtendimento(prev => ({ ...prev, profissional }));
}
// created_by também deve refletir o uid do usuário autenticado (se policy exigir)
let createdBy = authUid || user?.id || null;
if (!profissional || !dataAtendimento || !tipoAtendimento || !motivo) {
alert("Por favor, verifique: médico, data, tipo e motivo são obrigatórios.");
return;
}
const mappedKind = tipoAtendimento;
if (!ALLOWED_KINDS.includes(mappedKind)) {
alert(`Tipo inválido: "${tipoAtendimento}". Tipos aceitos: ${ALLOWED_KINDS.join(', ')}`);
return;
}
const startTime = inicio ? inicio + ":00" : null;
const endTime = termino ? termino + ":00" : null;
const payload = {
doctor_id: profissional,
date: dataAtendimento,
kind: mappedKind,
start_time: startTime,
end_time: endTime,
reason: motivo,
created_by: createdBy
};
// monta headers do POST usando authHeader já normalizado
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'application/json');
myHeaders.append('Authorization', authHeader);
if (API_KEY) myHeaders.append('apikey', API_KEY);
// envia
try {
const response = await fetch(ENDPOINT_CRIAR_EXCECAO, {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(payload),
redirect: 'follow'
});
const text = await response.text();
let result;
try { result = JSON.parse(text); } catch { result = { message: text }; }
if (response.ok || response.status === 201) {
alert(`Exceção criada com sucesso.`);
onCancel(true);
} else {
console.error("Erro ao criar exceção:", result);
alert(`Erro ao criar exceção. Status: ${response.status}. ${result.message || ''}`);
}
} catch (error) {
console.error("Erro na requisição para criar exceção:", error);
alert("Erro de comunicação com o servidor.");
}
};
// exibimos informações do médico logado de forma somente leitura (preenchidas automaticamente)
const [displayDoctorName, setDisplayDoctorName] = useState(user?.full_name || user?.name || '');
const displayDoctorId = doctorID || user?.doctor_id || user?.id || '';
// preenche dados do médico usando a função de informações do usuário (UserInfos / getUserInfo)
useEffect(() => {
let cancelled = false;
const resolvedDoctorId = doctorID || user?.doctor_id || user?.id || null;
const userName = user?.full_name || user?.name || user?.username || '';
const tryFillFromUserInfo = async () => {
try {
// primeiro, tenta usar getUserInfo() se disponível
let info = null;
if (typeof getUserInfo === 'function') {
try {
// se getUserInfo precisa de header, passe-o; caso contrário, a função interna deve usar auth
const authHeader = resolveAuthHeader();
// se getUserInfo aceita header, você pode adaptar; aqui tentamos chamar sem args
info = await getUserInfo();
} catch (e) {
// fallback: sem info
}
}
// se não veio info, use user
const profile = info || user || {};
const profileId = profile.doctor_id || profile.id || resolvedDoctorId;
const profileName = profile.full_name || profile.name || userName;
if (!cancelled && profileName) setDisplayDoctorName(profileName);
if (!cancelled && profileId) {
setDadosAtendimento(prev => ({ ...prev, profissional: prev.profissional || profileId }));
return;
}
// fallback: tentar buscar por nome via endpoint de médicos (usa auth header normalizado)
if (userName) {
const authHeader = resolveAuthHeader();
if (!authHeader) {
console.warn('No auth header available for GetDoctorByName lookup');
} else {
const doctor = await GetDoctorByName(userName, authHeader);
if (!cancelled && doctor) {
setDisplayDoctorName(doctor.full_name || doctor.name || userName);
setDadosAtendimento(prev => ({ ...prev, profissional: doctor.id || doctor.doctor_id || prev.profissional }));
return;
}
}
}
// último fallback
if (!cancelled) {
setDisplayDoctorName(userName || '');
if (resolvedDoctorId) setDadosAtendimento(prev => ({ ...prev, profissional: prev.profissional || resolvedDoctorId }));
}
} catch (err) {
if (cancelled) return;
console.warn('Erro ao preencher dados do médico via UserInfos/getUserInfo:', err);
setDisplayDoctorName(userName || '');
if (resolvedDoctorId) setDadosAtendimento(prev => ({ ...prev, profissional: prev.profissional || resolvedDoctorId }));
}
};
tryFillFromUserInfo();
return () => { cancelled = true; };
}, [doctorID, user, getUserInfo, getAuthorizationHeader]);
return (
<div className="form-container">
<form className="form-agendamento" onSubmit={handleSubmitExcecao}>
<h2 className="section-title">Informações da Nova Exceção</h2>
{/* Médico agora não é editável/visível — dados preenchidos automaticamente */}
<div className="campo-informacoes-atendimento">
<div className="campo-de-input">
<label>Tipo de exceção *</label>
<select name="tipoAtendimento" onChange={handleAtendimentoChange} value={dadosAtendimento.tipoAtendimento} required>
<option value="" disabled>Selecione o tipo de exceção</option>
<option value="disponibilidade_extra">Disponibilidade Extra</option>
<option value="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 (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>
</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;

View File

@ -522,13 +522,13 @@ const filtrarPorPaciente = (appointments) => {
<i className="bi bi-plus-circle"></i> Adicionar Consulta
</button>
<button
className="manage-button btn"
onClick={() => navigate("/secretaria/excecoes-disponibilidade")}
className="btn btn-primary"
onClick={() => navigate("/medico/excecoes-disponibilidade")}
>
<i className="bi bi-gear-fill me-1"></i> Gerenciar Exceções
</button>
<button
className="manage-button btn"
className="btn btn-primary"
onClick={() => navigate("/secretaria/disponibilidade")}
>
<i className="bi bi-gear-fill me-1"></i> Mudar Disponibilidade

View File

@ -1,6 +1,6 @@
import { useState, useEffect, useMemo, useCallback } from 'react';
import { useAuth } from '../../_assets/utils/AuthProvider'
import FormExcecaoDisponibilidade from '../../components/medico/FormExcecaoDisponibilidade';
import FormExcecaoDisponibilidade from '../../components/medico/FormExcecaoMedico';
import '../../_assets/css/components/agendamento/FormAgendamento.css';
import '../../_assets/css/pages/agendamento/Agendamento.css';

View File

@ -299,7 +299,6 @@ const DoctorRelatorioManager = () => {
)}
<div className="page-heading"><h3>Lista de Relatórios</h3></div>
<div className="page-content">
<section className="row">
<div className="col-12">
<div className="card">
@ -450,7 +449,6 @@ const DoctorRelatorioManager = () => {
</div>
</section>
</div>
</div>
);
};

View File

@ -3,7 +3,7 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import html2pdf from 'html2pdf.js';
import './styleMedico/NovoRelatorioAudio.css';
import '../../_assets/css/pages/medico/RelatorioAudio.css';
const NovoRelatorioAudio = () => {
const navigate = useNavigate();

View File

@ -469,7 +469,7 @@ const Agendamento = ({ setDictInfo }) => {
</div>
</div>
) : (
<div className="page-content table-paciente-container">
<div className="table-paciente-container">
<section className="row">
<div className="col-12">
<div className="card table-paciente-card">

View File

@ -466,13 +466,13 @@ const Agendamento = ({ setDictInfo }) => {
<i className="bi bi-plus-circle"></i> Adicionar Consulta
</button>
<button
className="manage-button btn"
className="btn btn-primary"
onClick={() => navigate("/secretaria/excecoes-disponibilidade")}
>
<i className="bi bi-gear-fill me-1"></i> Gerenciar Exceções
</button>
<button
className="manage-button btn"
className="btn btn-primary"
onClick={() => navigate("/secretaria/disponibilidade")}
>
<i className="bi bi-gear-fill me-1"></i> Mudar Disponibilidade
@ -792,7 +792,7 @@ const Agendamento = ({ setDictInfo }) => {
</div>
</div>
) : (
<div className="page-content table-paciente-container">
<div className="table-paciente-container">
<section className="row">
<div className="col-12">
<div className="card table-paciente-card">

View File

@ -239,7 +239,6 @@ const LaudoManager = () => {
return (
<div>
<div className="page-heading"><h3>Lista de Relatórios</h3></div>
<div className="page-content">
<section className="row">
<div className="col-12">
<div className="card">
@ -404,7 +403,6 @@ const LaudoManager = () => {
</div>
</div>
</section>
</div>
{showModal && relatorioModal && (

View File

@ -211,7 +211,7 @@ function TableDoctor({setDictInfo}) {
<div className="page-heading">
<h3>Lista de Médicos</h3>
</div>
<div className="page-content table-doctor-container">
<div className="table-doctor-container">
<section className="row">
<div className="col-12">
<div className="card table-doctor-card">

View File

@ -318,7 +318,7 @@ function TablePaciente({ setPatientID, setDictInfo }) {
<div className="page-heading">
<h3>Lista de Pacientes</h3>
</div>
<div className="page-content table-paciente-container">
<div className="table-paciente-container">
<section className="row">
<div className="col-12">
<div className="card table-paciente-card">

View File

@ -42,6 +42,7 @@ function PerfilMedico() {
<Route path="/relatorios" element={<DoctorRelatorioManager />} />
<Route path="/agendamento" element={<DoctorAgendamentoManager setDictInfo={setDictInfo}/>} />
<Route path="/agendamento/edit" element={<DoctorAgendamentoEditPage DictInfo={dictInfo} setDictInfo={setDictInfo}/>} />
<Route path="/excecoes-disponibilidade" element={<ExcecoesDisponibilidadeDoctor />} />
<Route path="/chat" element={<Chat />} />
</Routes>
</div>