diff --git a/src/App.js b/src/App.js index 99ac1ec..c482091 100644 --- a/src/App.js +++ b/src/App.js @@ -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"; diff --git a/src/components/medico/FormExcecaoDisponibilidade.jsx b/src/components/medico/FormExcecaoDisponibilidade.jsx index 654b811..a75144b 100644 --- a/src/components/medico/FormExcecaoDisponibilidade.jsx +++ b/src/components/medico/FormExcecaoDisponibilidade.jsx @@ -335,7 +335,7 @@ const FormCriarExcecao = ({ onCancel, doctorID }) => { diff --git a/src/components/medico/FormExcecaoMedico.jsx b/src/components/medico/FormExcecaoMedico.jsx new file mode 100644 index 0000000..c59d2a0 --- /dev/null +++ b/src/components/medico/FormExcecaoMedico.jsx @@ -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 " + 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 ( +
+
+

Informações da Nova Exceção

+ + {/* Médico agora não é editável/visível — dados preenchidos automaticamente */} +
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+
+ +
+
+ + +
+
+
+ +
+ + +
+
+
+ ); +}; + +export default FormCriarExcecao; \ No newline at end of file diff --git a/src/pages/medico/CadastroAgendamento.jsx b/src/pages/medico/CadastroAgendamento.jsx index 411f836..56ee26b 100644 --- a/src/pages/medico/CadastroAgendamento.jsx +++ b/src/pages/medico/CadastroAgendamento.jsx @@ -522,13 +522,13 @@ const filtrarPorPaciente = (appointments) => { Adicionar Consulta