561 lines
18 KiB
JavaScript
561 lines
18 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Container,
|
|
Grid,
|
|
Card,
|
|
CardContent,
|
|
Typography,
|
|
Button,
|
|
Avatar,
|
|
Box,
|
|
Chip,
|
|
CircularProgress,
|
|
Alert,
|
|
Paper,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
Stepper,
|
|
Step,
|
|
StepLabel,
|
|
Checkbox,
|
|
FormControlLabel
|
|
} from '@mui/material';
|
|
import {
|
|
ArrowBack,
|
|
CalendarToday,
|
|
AccessTime,
|
|
Person,
|
|
LocalHospital,
|
|
CheckCircle,
|
|
Email,
|
|
Sms
|
|
} from '@mui/icons-material';
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
import { Link } from "react-router-dom";
|
|
import Swal from "sweetalert2";
|
|
import { getAccessToken } from "../../utils/auth.js";
|
|
import { getPatientId } from "../../utils/userInfo";
|
|
import { getUserRole } from '../../utils/userInfo';
|
|
|
|
const AgendarConsulta = () => {
|
|
const { medicoId } = useParams();
|
|
const navigate = useNavigate();
|
|
const [medico, setMedico] = useState(null);
|
|
const [horariosDisponiveis, setHorariosDisponiveis] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [dataSelecionada, setDataSelecionada] = useState('');
|
|
const [horarioSelecionado, setHorarioSelecionado] = useState(null);
|
|
const [modalConfirmacao, setModalConfirmacao] = useState(false);
|
|
const [agendando, setAgendando] = useState(false);
|
|
const [activeStep, setActiveStep] = useState(0);
|
|
const [enviarEmail, setEnviarEmail] = useState(true);
|
|
const [enviarSMS, setEnviarSMS] = useState(true);
|
|
const [minDate, setMinDate] = useState("");
|
|
const [carregandoHorarios, setCarregandoHorarios] = useState(false);
|
|
const [formData, setFormData] = useState({
|
|
scheduled_date: "",
|
|
scheduled_time: "",
|
|
chief_complaint: "",
|
|
patient_notes: ""
|
|
});
|
|
let [confirmationModal, setConfirmationModal] = useState(false);
|
|
const role = getUserRole();
|
|
const tokenUsuario = getAccessToken();
|
|
const patientId = getPatientId();
|
|
|
|
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
|
const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
|
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
apikey: supabaseAK,
|
|
Authorization: `Bearer ${tokenUsuario}`,
|
|
};
|
|
|
|
const handleConfirmationModal = async () => {
|
|
if (!dataSelecionada || !horarioSelecionado) {
|
|
alert("Selecione uma data e horário válidos");
|
|
return;
|
|
}
|
|
|
|
const confirm = window.confirm(`
|
|
Confirmar agendamento:
|
|
|
|
Médico: Dr. ${medico?.nome}
|
|
Especialidade: ${medico?.especialidade}
|
|
Data: ${new Date(dataSelecionada).toLocaleDateString('pt-BR')}
|
|
Horário: ${horarioSelecionado ? horarioSelecionado.datetime.split("T")[1].substring(0, 5) : ''}
|
|
Valor: R$ ${medico?.valorConsulta}
|
|
|
|
Deseja confirmar?
|
|
`);
|
|
|
|
if (confirm) {
|
|
await confirmarAgendamento();
|
|
|
|
alert(`Consulta marcada com sucesso! Sua consulta com Dr. ${medico.nome} foi agendada.`);
|
|
navigate(`/${role}/consultalist`);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const getToday = () => {
|
|
const today = new Date();
|
|
const offset = today.getTimezoneOffset();
|
|
today.setMinutes(today.getMinutes() - offset);
|
|
return today.toISOString().split("T")[0];
|
|
};
|
|
|
|
setMinDate(getToday());
|
|
}, []);
|
|
useEffect(() => {
|
|
carregarMedicoEHorarios();
|
|
}, [medicoId]);
|
|
|
|
const carregarMedicoEHorarios = async () => {
|
|
setLoading(true);
|
|
try {
|
|
// Buscar dados do médico
|
|
const medicoResponse = await fetch(
|
|
`${supabaseUrl}/rest/v1/doctors?id=eq.${medicoId}`,
|
|
{ headers }
|
|
);
|
|
|
|
if (medicoResponse.ok) {
|
|
const medicoData = await medicoResponse.json();
|
|
if (medicoData.length > 0) {
|
|
const doctorData = medicoData[0];
|
|
setMedico({
|
|
id: doctorData.id,
|
|
nome: doctorData.full_name,
|
|
especialidade: doctorData.specialty,
|
|
valorConsulta: 250, // Valor fixo por enquanto
|
|
foto: '',
|
|
biografia: doctorData.bio || 'Especialista em ' + doctorData.specialty
|
|
});
|
|
} else {
|
|
throw new Error('Médico não encontrado');
|
|
}
|
|
} else {
|
|
throw new Error('Erro ao carregar dados do médico');
|
|
}
|
|
|
|
setLoading(false);
|
|
} catch (error) {
|
|
console.error('Erro ao carregar dados:', error);
|
|
setMedico(medicoMock);
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// Função para buscar horários disponíveis
|
|
const fetchHorariosDisponiveis = async (date) => {
|
|
if (!medicoId || !date) {
|
|
setHorariosDisponiveis([]);
|
|
return;
|
|
}
|
|
|
|
setCarregandoHorarios(true);
|
|
|
|
const startDate = `${date}T00:00:00.000Z`;
|
|
const endDate = `${date}T23:59:59.999Z`;
|
|
|
|
const payload = {
|
|
doctor_id: medicoId,
|
|
start_date: startDate,
|
|
end_date: endDate,
|
|
appointment_type: "presencial",
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`${supabaseUrl}/functions/v1/get-available-slots`,
|
|
{
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify(payload),
|
|
}
|
|
);
|
|
|
|
const data = await response.json();
|
|
|
|
console.log("🔍 AgendarConsultas - Resposta da Edge Function:", data);
|
|
|
|
if (!response.ok) throw new Error(data.error || "Erro ao buscar horários");
|
|
|
|
// Usar exatamente o mesmo formato do AgendaForm
|
|
const slotsDisponiveis = (data?.slots || []).filter((s) => s.available);
|
|
|
|
console.log("✅ Slots disponíveis após filtro:", slotsDisponiveis);
|
|
console.log("🔍 Todos os slots (antes do filtro):", data?.slots);
|
|
console.log("❌ Slots NÃO disponíveis:", (data?.slots || []).filter((s) => !s.available));
|
|
|
|
console.log("✅ AgendarConsultas - Slots disponíveis após filtro:", slotsDisponiveis);
|
|
|
|
setHorariosDisponiveis(slotsDisponiveis);
|
|
|
|
if (slotsDisponiveis.length === 0) {
|
|
alert("Nenhum horário disponível para este dia.");
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao buscar horários disponíveis:", error);
|
|
setHorariosDisponiveis([]);
|
|
alert("Não foi possível obter os horários disponíveis.");
|
|
} finally {
|
|
setCarregandoHorarios(false);
|
|
}
|
|
};
|
|
|
|
// Atualizar horários quando a data muda
|
|
useEffect(() => {
|
|
if (dataSelecionada && medicoId) {
|
|
fetchHorariosDisponiveis(dataSelecionada);
|
|
}
|
|
}, [dataSelecionada, medicoId]);
|
|
|
|
const selecionarHorario = (horario) => {
|
|
setHorarioSelecionado(horario);
|
|
setModalConfirmacao(true);
|
|
setActiveStep(0);
|
|
};
|
|
|
|
const confirmarAgendamento = async () => {
|
|
setAgendando(true);
|
|
|
|
try {
|
|
if (!horarioSelecionado || !horarioSelecionado.datetime) {
|
|
throw new Error("Horário não selecionado corretamente");
|
|
}
|
|
|
|
// Usar exatamente o mesmo formato que o AgendaForm
|
|
const scheduled_at = horarioSelecionado.datetime;
|
|
|
|
const payload = {
|
|
patient_id: patientId,
|
|
doctor_id: medicoId,
|
|
scheduled_at,
|
|
duration_minutes: 30,
|
|
appointment_type: "presencial",
|
|
chief_complaint: formData.chief_complaint || "Consulta agendada pelo paciente",
|
|
patient_notes: formData.patient_notes || "",
|
|
created_by: patientId,
|
|
};
|
|
|
|
const response = await fetch(
|
|
`${supabaseUrl}/rest/v1/appointments`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
...headers,
|
|
Prefer: "return=representation",
|
|
},
|
|
body: JSON.stringify(payload),
|
|
}
|
|
);
|
|
|
|
if (response.ok) {
|
|
const consultaCriada = await response.json();
|
|
console.log("Consulta criada:", consultaCriada);
|
|
|
|
setActiveStep(2);
|
|
setAgendando(false);
|
|
|
|
// Aqui você pode adicionar envio de SMS se necessário
|
|
// if (enviarSMS) {
|
|
// await sendSMS(telefone, mensagem, patientId);
|
|
// }
|
|
|
|
} else {
|
|
const error = await response.json();
|
|
console.error("Erro da API:", error);
|
|
throw new Error("Não foi possível criar a consulta");
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Erro no agendamento:', error);
|
|
alert(error.message || "Erro ao realizar agendamento. Tente novamente.");
|
|
setAgendando(false);
|
|
}
|
|
};
|
|
|
|
const finalizarAgendamento = () => {
|
|
setModalConfirmacao(false);
|
|
navigate(`/${role}/consultalist`);
|
|
};
|
|
|
|
// Não precisamos mais da linha datasDisponiveis, pois usamos a Edge Function
|
|
const horariosDaData = horariosDisponiveis.filter(h => h.data === dataSelecionada);
|
|
|
|
const renderStepContent = (step) => {
|
|
switch (step) {
|
|
case 0:
|
|
return (
|
|
<Box sx={{ mt: 2 }}>
|
|
<Typography variant="h6" gutterBottom>
|
|
Confirme os dados da consulta:
|
|
</Typography>
|
|
|
|
<Paper sx={{ p: 2, mb: 2 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
|
<Person sx={{ mr: 1, color: 'primary.main' }} />
|
|
<Typography><strong>Médico:</strong> Dr. {medico.nome}</Typography>
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
|
<LocalHospital sx={{ mr: 1, color: 'primary.main' }} />
|
|
<Typography><strong>Especialidade:</strong> {medico.especialidade}</Typography>
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
|
<CalendarToday sx={{ mr: 1, color: 'primary.main' }} />
|
|
<Typography><strong>Data:</strong> {new Date(dataSelecionada).toLocaleDateString('pt-BR')}</Typography>
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
|
<AccessTime sx={{ mr: 1, color: 'primary.main' }} />
|
|
<Typography><strong>Horário:</strong> {horarioSelecionado ? horarioSelecionado.datetime.split("T")[1].substring(0, 5) : ''}</Typography>
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
<Typography><strong>Valor:</strong> R$ {medico.valorConsulta}</Typography>
|
|
</Box>
|
|
</Paper>
|
|
|
|
<Alert severity="info">
|
|
Chegue com 15 minutos de antecedência para o atendimento.
|
|
</Alert>
|
|
</Box>
|
|
);
|
|
|
|
case 1:
|
|
return (
|
|
<Box sx={{ mt: 2 }}>
|
|
<Typography variant="h6" gutterBottom>
|
|
Escolha como deseja receber as confirmações:
|
|
</Typography>
|
|
|
|
<FormControlLabel
|
|
control={
|
|
<Checkbox
|
|
checked={enviarEmail}
|
|
onChange={(e) => setEnviarEmail(e.target.checked)}
|
|
icon={<Email />}
|
|
checkedIcon={<Email color="primary" />}
|
|
/>
|
|
}
|
|
label="Receber confirmação por E-mail"
|
|
sx={{ mb: 2, display: 'block' }}
|
|
/>
|
|
|
|
<FormControlLabel
|
|
control={
|
|
<Checkbox
|
|
checked={enviarSMS}
|
|
onChange={(e) => setEnviarSMS(e.target.checked)}
|
|
icon={<Sms />}
|
|
checkedIcon={<Sms color="primary" />}
|
|
/>
|
|
}
|
|
label="Receber confirmação por SMS"
|
|
sx={{ display: 'block' }}
|
|
/>
|
|
|
|
<Alert severity="info" sx={{ mt: 2 }}>
|
|
Você também receberá um lembrete 24 horas antes da consulta.
|
|
</Alert>
|
|
</Box>
|
|
);
|
|
|
|
case 2:
|
|
return (
|
|
<Box sx={{ mt: 2, textAlign: 'center' }}>
|
|
<CheckCircle sx={{ fontSize: 60, color: 'success.main', mb: 2 }} />
|
|
<Typography variant="h5" gutterBottom color="success.main">
|
|
Consulta Agendada com Sucesso!
|
|
</Typography>
|
|
<Typography variant="body1" sx={{ mb: 2 }}>
|
|
Sua consulta foi agendada para {new Date(dataSelecionada).toLocaleDateString('pt-BR')} às {horarioSelecionado ? horarioSelecionado.datetime.split("T")[1].substring(0, 5) : ''}
|
|
</Typography>
|
|
<Alert severity="success">
|
|
A consulta foi adicionada à agenda do Dr. {medico.nome} e as confirmações foram enviadas.
|
|
</Alert>
|
|
</Box>
|
|
);
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<Container sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '50vh' }}>
|
|
<CircularProgress />
|
|
<Typography sx={{ ml: 2 }}>Carregando horários...</Typography>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
if (!medico) {
|
|
return (
|
|
<Container sx={{ mt: 4 }}>
|
|
<Alert severity="error">Médico não encontrado</Alert>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className='page-wrapper'>
|
|
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
|
|
<Button
|
|
startIcon={<ArrowBack />}
|
|
onClick={() => navigate("/paciente/medicosdisponiveis")}
|
|
className='btn btn-secondary'
|
|
>
|
|
Voltar para Médicos
|
|
</Button>
|
|
|
|
{/* Cabeçalho do Médico */}
|
|
<Card sx={{ mb: 4 }}>
|
|
<CardContent>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
|
<Avatar
|
|
sx={{
|
|
width: 60,
|
|
height: 60,
|
|
mr: 2,
|
|
bgcolor: 'primary.main',
|
|
fontSize: '1.5rem',
|
|
fontWeight: 'bold'
|
|
}}
|
|
>
|
|
{medico.nome.split(' ').map(n => n[0]).join('')}
|
|
</Avatar>
|
|
<Box>
|
|
<Typography variant="h5" fontWeight="bold">
|
|
Dr. {medico.nome}
|
|
</Typography>
|
|
<Chip label={medico.especialidade} color="primary" size="small" />
|
|
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
|
{medico.biografia}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
<form>
|
|
<hr />
|
|
<h3>Informações do atendimento</h3>
|
|
<div className="row">
|
|
<div className="col-md-6">
|
|
<div className="form-group">
|
|
<label>Data<span className="text-danger">*</span></label>
|
|
<div>
|
|
<input
|
|
type="date"
|
|
className="form-control"
|
|
min={minDate}
|
|
value={dataSelecionada}
|
|
onChange={(e) => setDataSelecionada(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="col-md-6">
|
|
<div className="form-group">
|
|
<label>Horários Disponíveis<span className="text-danger">*</span></label>
|
|
<div>
|
|
<select
|
|
className="form-control"
|
|
value={horarioSelecionado ? horarioSelecionado.datetime.split("T")[1].substring(0, 5) : ""}
|
|
onChange={(e) => {
|
|
const horaValue = e.target.value;
|
|
const horario = horariosDisponiveis.find(slot => {
|
|
const hora = slot.datetime.split("T")[1].substring(0, 5);
|
|
return hora === horaValue;
|
|
});
|
|
setHorarioSelecionado(horario);
|
|
}}
|
|
disabled={carregandoHorarios || !horariosDisponiveis.length}
|
|
>
|
|
<option value="">
|
|
{carregandoHorarios
|
|
? "Carregando horários..."
|
|
: horariosDisponiveis.length
|
|
? "Selecione um horário"
|
|
: "Nenhum horário disponível"}
|
|
</option>
|
|
{horariosDisponiveis.map((slot) => {
|
|
const hora = slot.datetime.split("T")[1].substring(0, 5);
|
|
return (
|
|
<option key={slot.datetime} value={hora}>
|
|
{hora}
|
|
</option>
|
|
);
|
|
})}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="form-group">
|
|
<label>Motivo da consulta</label>
|
|
<input
|
|
type="text"
|
|
className="form-control"
|
|
value={formData.chief_complaint}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, chief_complaint: e.target.value }))}
|
|
placeholder="Ex: Dor no peito, consulta de rotina..."
|
|
/>
|
|
</div>
|
|
<div className="form-group">
|
|
<label>Observações</label>
|
|
<textarea
|
|
cols="30"
|
|
rows="4"
|
|
className="form-control"
|
|
value={formData.patient_notes}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, patient_notes: e.target.value }))}
|
|
placeholder="Observações adicionais (opcional)"
|
|
></textarea>
|
|
</div>
|
|
|
|
<div className="form-group">
|
|
<label className="display-block">Status da consulta</label>
|
|
<div className="form-check form-check-inline">
|
|
<input
|
|
className="form-check-input"
|
|
type="checkbox"
|
|
name="status"
|
|
id="product_active"
|
|
value="option1"
|
|
defaultChecked
|
|
/>
|
|
<label
|
|
className="form-check-label"
|
|
htmlFor="product_active"
|
|
>
|
|
Receber confirmação por SMS
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="m-t-20 text-center">
|
|
<button
|
|
className="btn btn-primary submit-btn"
|
|
type="button"
|
|
onClick={handleConfirmationModal}
|
|
disabled={agendando || !dataSelecionada || !horarioSelecionado}
|
|
>
|
|
{agendando ? "Agendando..." : "Marcar consulta"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</Container>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AgendarConsulta; |