408 lines
18 KiB
JavaScript
408 lines
18 KiB
JavaScript
import { useState, useEffect } from "react";
|
||
import { useAuth } from "../../_assets/utils/AuthProvider";
|
||
import { GetAllDoctors, GetDoctorByName } from '../../_assets/utils/Functions-Endpoints/Doctor';
|
||
import API_KEY from "../../_assets/utils/apiKeys";
|
||
|
||
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();
|
||
const [dadosAtendimento, setDadosAtendimento] = useState({
|
||
profissional: doctorID || '',
|
||
crm: '',
|
||
tipoAtendimento: '',
|
||
dataAtendimento: '',
|
||
inicio: '',
|
||
termino: '',
|
||
motivo: ''
|
||
});
|
||
|
||
const [todosProfissionais, setTodosProfissionais] = useState([]);
|
||
const [profissionaisFiltrados, setProfissionaisFiltrados] = useState([]);
|
||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||
|
||
const [doctorSearchName, setDoctorSearchName] = useState('');
|
||
const [searchingDoctor, setSearchingDoctor] = useState(false);
|
||
|
||
const handleAtendimentoChange = (e) => {
|
||
const { value, name } = e.target;
|
||
setDadosAtendimento(prev => ({
|
||
...prev,
|
||
[name]: value
|
||
}));
|
||
};
|
||
|
||
// Preencher automaticamente usando GetDoctorByName (se disponível) ou dados do usuário como fallback
|
||
useEffect(() => {
|
||
let cancelled = false;
|
||
const loggedDoctorId = doctorID || user?.doctor_id || user?.id || null;
|
||
const userName = user?.full_name || user?.name || user?.username || '';
|
||
const userCrm = user?.crm || user?.crm_number || user?.crmFormatted || '';
|
||
|
||
const tryFill = async () => {
|
||
// se não há nome para buscar, usa fallback local
|
||
if (!userName) {
|
||
setDadosAtendimento(prev => ({
|
||
...prev,
|
||
profissional: prev.profissional || loggedDoctorId || '',
|
||
crm: prev.crm || userCrm || ''
|
||
}));
|
||
setDoctorSearchName(prev => prev || '');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const authHeader = getAuthorizationHeader ? getAuthorizationHeader() : '';
|
||
const doctor = await GetDoctorByName(userName, authHeader);
|
||
if (cancelled) return;
|
||
|
||
if (doctor) {
|
||
const crmValue = doctor.crm || doctor.crm_number || doctor.crmFormatted || userCrm || '';
|
||
const idValue = doctor.id || loggedDoctorId || '';
|
||
setDadosAtendimento(prev => ({
|
||
...prev,
|
||
profissional: prev.profissional || idValue,
|
||
crm: prev.crm || crmValue
|
||
}));
|
||
setDoctorSearchName(prev => prev || (doctor.full_name || userName));
|
||
} else {
|
||
// fallback para dados do usuário quando busca não retorna resultado
|
||
setDadosAtendimento(prev => ({
|
||
...prev,
|
||
profissional: prev.profissional || loggedDoctorId || '',
|
||
crm: prev.crm || userCrm || ''
|
||
}));
|
||
setDoctorSearchName(prev => prev || userName);
|
||
}
|
||
} catch (err) {
|
||
console.warn('GetDoctorByName falhou, usando fallback do usuário:', err);
|
||
if (cancelled) return;
|
||
setDadosAtendimento(prev => ({
|
||
...prev,
|
||
profissional: prev.profissional || loggedDoctorId || '',
|
||
crm: prev.crm || userCrm || ''
|
||
}));
|
||
setDoctorSearchName(prev => prev || userName);
|
||
}
|
||
};
|
||
|
||
tryFill();
|
||
return () => { cancelled = true; };
|
||
}, [doctorID, user, getAuthorizationHeader]);
|
||
|
||
useEffect(() => {
|
||
const loadDoctors = async () => {
|
||
setSearchingDoctor(true);
|
||
let authHeader = '';
|
||
try { authHeader = getAuthorizationHeader ? getAuthorizationHeader() : ''; } catch {}
|
||
try {
|
||
const Medicos = await GetAllDoctors(authHeader);
|
||
const medicosArray = Array.isArray(Medicos) ? Medicos : [];
|
||
setTodosProfissionais(medicosArray);
|
||
|
||
// preencher automaticamente com médico logado (se encontrado na lista)
|
||
const loggedDoctorId = doctorID || user?.doctor_id || user?.id || null;
|
||
if (loggedDoctorId) {
|
||
const match = medicosArray.find(d =>
|
||
String(d.id) === String(loggedDoctorId) ||
|
||
String(d.doctor_id || '').toLowerCase() === String(loggedDoctorId).toLowerCase()
|
||
);
|
||
if (match) {
|
||
const crmValue = match.crm || match.crm_number || match.crmFormatted || '';
|
||
setDadosAtendimento(prev => ({
|
||
...prev,
|
||
profissional: prev.profissional || match.id,
|
||
crm: prev.crm || crmValue
|
||
}));
|
||
setDoctorSearchName(prev => prev || (match.full_name || ''));
|
||
} else {
|
||
// se não encontrou na lista mas user tem crm, preenche crm
|
||
const userCrm = user?.crm || user?.crm_number || '';
|
||
if (userCrm) {
|
||
setDadosAtendimento(prev => ({ ...prev, crm: prev.crm || userCrm }));
|
||
}
|
||
}
|
||
} else {
|
||
// se não há loggedDoctorId, mas user tem crm, preenche crm
|
||
const userCrm = user?.crm || user?.crm_number || '';
|
||
if (userCrm) {
|
||
setDadosAtendimento(prev => ({ ...prev, crm: prev.crm || userCrm }));
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('Erro ao carregar médicos:', err);
|
||
setTodosProfissionais([]);
|
||
} finally {
|
||
setSearchingDoctor(false);
|
||
}
|
||
};
|
||
loadDoctors();
|
||
}, [getAuthorizationHeader, doctorID, user]);
|
||
|
||
const handleSearchProfissional = (e) => {
|
||
const term = e.target.value;
|
||
setDoctorSearchName(term);
|
||
if (term.trim() === '') {
|
||
setProfissionaisFiltrados([]);
|
||
setIsDropdownOpen(false);
|
||
return;
|
||
}
|
||
const filtered = todosProfissionais.filter(p =>
|
||
((p.full_name || '').toLowerCase().includes(term.toLowerCase()) ||
|
||
(String(p.crm || p.crm_number || '').toLowerCase().includes(term.toLowerCase())))
|
||
);
|
||
setProfissionaisFiltrados(filtered);
|
||
setIsDropdownOpen(filtered.length > 0);
|
||
};
|
||
|
||
const handleSelectProfissional = (profissional) => {
|
||
const crmValue = profissional.crm || profissional.crm_number || profissional.crmFormatted || '';
|
||
setDadosAtendimento(prev => ({ ...prev, profissional: profissional.id, crm: crmValue }));
|
||
setDoctorSearchName(profissional.full_name || '');
|
||
setProfissionaisFiltrados([]);
|
||
setIsDropdownOpen(false);
|
||
};
|
||
|
||
// lista simples de valores permitidos
|
||
const ALLOWED_KINDS = ['disponibilidade_extra', 'bloqueio'];
|
||
|
||
const handleSubmitExcecao = async (e) => {
|
||
e.preventDefault();
|
||
console.log("Tentando criar Exceção.");
|
||
|
||
let { profissional, crm, dataAtendimento, tipoAtendimento, inicio, termino, motivo } = dadosAtendimento;
|
||
|
||
// agora a validação principal é pelo CRM (campo obrigatório)
|
||
if (!crm || !dataAtendimento || !tipoAtendimento || !motivo) {
|
||
alert("Por favor, preencha o CRM do médico, Data, Tipo e Motivo.");
|
||
return;
|
||
}
|
||
|
||
// se o ID do profissional não foi selecionado, tenta resolver pelo CRM usando lista carregada
|
||
if (!profissional) {
|
||
const match = todosProfissionais.find(d => {
|
||
const dcrm = String(d.crm || d.crm_number || d.crmFormatted || '').trim();
|
||
return dcrm && dcrm.toLowerCase() === String(crm).toLowerCase();
|
||
});
|
||
if (match) {
|
||
profissional = match.id;
|
||
} else {
|
||
alert("Médico com o CRM informado não encontrado. Selecione a partir da lista ou verifique o CRM.");
|
||
return;
|
||
}
|
||
} else {
|
||
// se ID estiver setado, opcional: conferir se CRM coincide com o cadastrado (se tiver)
|
||
const sel = todosProfissionais.find(d => d.id === profissional);
|
||
if (sel) {
|
||
const dcrm = String(sel.crm || sel.crm_number || sel.crmFormatted || '').trim();
|
||
if (dcrm && dcrm.toLowerCase() !== String(crm).toLowerCase()) {
|
||
// apenas alerta, não bloqueia — mantém ID para envio como pedido
|
||
if (!window.confirm("O CRM informado não corresponde ao CRM do médico selecionado. Deseja continuar?")) {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// usa diretamente o value selecionado (já definido no <select>) e valida
|
||
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;
|
||
|
||
let authHeader = "";
|
||
try {
|
||
authHeader = getAuthorizationHeader ? getAuthorizationHeader() : "";
|
||
} catch (err) {
|
||
console.warn("Não foi possível obter Authorization header via useAuth()", err);
|
||
}
|
||
|
||
let createdBy = user?.id || null;
|
||
if (!createdBy && typeof getUserInfo === "function") {
|
||
try {
|
||
const info = await getUserInfo();
|
||
createdBy = info?.id || info?.profile?.id || null;
|
||
} catch (err) {
|
||
console.warn("getUserInfo falhou:", err);
|
||
}
|
||
}
|
||
|
||
if (!createdBy) {
|
||
try {
|
||
const stored = localStorage.getItem("user");
|
||
if (stored) {
|
||
const parsed = JSON.parse(stored);
|
||
createdBy = parsed?.id || parsed?.user?.id || null;
|
||
}
|
||
} catch {}
|
||
}
|
||
|
||
const raw = JSON.stringify({
|
||
doctor_id: profissional,
|
||
date: dataAtendimento,
|
||
kind: mappedKind,
|
||
start_time: startTime,
|
||
end_time: endTime,
|
||
reason: motivo,
|
||
created_by: createdBy
|
||
});
|
||
|
||
var myHeaders = new Headers();
|
||
if (authHeader) myHeaders.append("Authorization", authHeader);
|
||
myHeaders.append("Content-Type", "application/json");
|
||
if (API_KEY) myHeaders.append("apikey", API_KEY);
|
||
|
||
var requestOptions = {
|
||
method: 'POST',
|
||
headers: myHeaders,
|
||
body: raw,
|
||
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);
|
||
} 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">
|
||
{/* Busca por nome usando filtragem local */}
|
||
<div className="campo-de-input campo-de-input-container">
|
||
<label>Nome do médico</label>
|
||
<input
|
||
type="text"
|
||
name="doctorSearchName"
|
||
placeholder="Digite o nome do médico"
|
||
value={doctorSearchName}
|
||
onChange={handleSearchProfissional}
|
||
autoComplete="off"
|
||
/>
|
||
{isDropdownOpen && profissionaisFiltrados.length > 0 && (
|
||
<div className="dropdown-profissionais">
|
||
{profissionaisFiltrados.map(p => (
|
||
<div key={p.id} className="dropdown-item" onClick={() => handleSelectProfissional(p)}>
|
||
{p.full_name}
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
{searchingDoctor && <small>Carregando médicos...</small>}
|
||
</div>
|
||
|
||
{/* CRM do profissional (agora campo obrigatório de validação) */}
|
||
<div className="campo-de-input">
|
||
<label>CRM do médico *</label>
|
||
<input
|
||
type="text"
|
||
name="crm"
|
||
required
|
||
placeholder="Ex: 12345-SP"
|
||
value={dadosAtendimento.crm}
|
||
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>Selecione o tipo de exceção</option>
|
||
<option value="disponibilidade_extra" >Liberação</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; |