diff --git a/components/schedule/schedule-form.tsx b/components/schedule/schedule-form.tsx index becac35..89ffb05 100644 --- a/components/schedule/schedule-form.tsx +++ b/components/schedule/schedule-form.tsx @@ -14,8 +14,10 @@ import { Textarea } from "@/components/ui/textarea"; import { Calendar as CalendarShadcn } from "@/components/ui/calendar"; import { format, addDays } from "date-fns"; import { User, StickyNote, Calendar } from "lucide-react"; +import {smsService } from "@/services/Sms.mjs" import { toast } from "@/hooks/use-toast"; + export default function ScheduleForm() { // Estado do usuário e role const [role, setRole] = useState("paciente"); @@ -207,46 +209,118 @@ export default function ScheduleForm() { }, [selectedDoctor, selectedDate, fetchAvailableSlots]); // 🔹 Submeter agendamento - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); + // 🔹 Submeter agendamento + // 🔹 Submeter agendamento +// 🔹 Submeter agendamento +// 🔹 Submeter agendamento +const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); - const isSecretaryLike = ["secretaria", "admin", "gestor"].includes(role); - const patientId = isSecretaryLike ? selectedPatient : userId; + const isSecretaryLike = ["secretaria", "admin", "gestor"].includes(role); + const patientId = isSecretaryLike ? selectedPatient : userId; + + if (!patientId || !selectedDoctor || !selectedDate || !selectedTime) { + toast({ title: "Campos obrigatórios", description: "Preencha todos os campos." }); + return; + } + + try { + const body = { + doctor_id: selectedDoctor, + patient_id: patientId, + scheduled_at: `${selectedDate}T${selectedTime}:00`, + duration_minutes: Number(duracao), + notes, + appointment_type: tipoConsulta, + }; + + // ✅ mantém o fluxo original de criação (funcional) + await appointmentsService.create(body); + + const dateFormatted = selectedDate.split("-").reverse().join("/"); + + toast({ + title: "Consulta agendada!", + description: `Consulta marcada para ${dateFormatted} às ${selectedTime} com o(a) médico(a) ${ + doctors.find((d) => d.id === selectedDoctor)?.full_name || "" + }.`, + }); + +let phoneNumber = "+5511999999999"; // fallback + +try { + if (isSecretaryLike) { + // Secretária/admin → telefone do paciente selecionado + const patient = patients.find((p: any) => p.id === patientId); + + // Pacientes criados no sistema podem ter phone ou phone_mobile + const rawPhone = patient?.phone || patient?.phone_mobile || null; + + if (rawPhone) phoneNumber = rawPhone; + } else { + // Paciente → telefone vem do perfil do próprio usuário logado + const me = await usersService.getMe(); + + +const rawPhone = + me?.profile?.phone || + (typeof me?.profile === "object" && "phone_mobile" in me.profile ? (me.profile as any).phone_mobile : null) || + (typeof me === "object" && "user_metadata" in me ? (me as any).user_metadata?.phone : null) || + null; + + if (rawPhone) phoneNumber = rawPhone; + } + + // 🔹 Normaliza para formato internacional (+55) + if (phoneNumber) { + phoneNumber = phoneNumber.replace(/\D/g, ""); + if (!phoneNumber.startsWith("55")) phoneNumber = `55${phoneNumber}`; + phoneNumber = `+${phoneNumber}`; + } + + console.log("📞 Telefone usado:", phoneNumber); +} catch (err) { + console.warn("⚠️ Não foi possível obter telefone do paciente:", err); +} + + + // 💬 envia o SMS de confirmação + // 💬 Envia o SMS de lembrete (sem mostrar nada ao paciente) +// 💬 Envia o SMS de lembrete (somente loga no console, não mostra no sistema) +try { + const smsRes = await smsService.sendSms({ + phone_number: phoneNumber, + message: `Lembrete: sua consulta é em ${dateFormatted} às ${selectedTime} na Clínica MediConnect.`, + patient_id: patientId, + }); + + if (smsRes?.success) { + console.log("✅ SMS enviado com sucesso:", smsRes.message_sid); + } else { + console.warn("⚠️ Falha no envio do SMS:", smsRes); + } +} catch (smsErr) { + console.error("❌ Erro ao enviar SMS:", smsErr); +} + + + + + // 🧹 limpa os campos + setSelectedDoctor(""); + setSelectedDate(""); + setSelectedTime(""); + setNotes(""); + setSelectedPatient(""); + } catch (err) { + console.error("❌ Erro ao agendar consulta:", err); + toast({ title: "Erro", description: "Falha ao agendar consulta." }); + } +}; - if (!patientId || !selectedDoctor || !selectedDate || !selectedTime) { - toast({ title: "Campos obrigatórios", description: "Preencha todos os campos." }); - return; - } - try { - const body = { - doctor_id: selectedDoctor, - patient_id: patientId, - scheduled_at: `${selectedDate}T${selectedTime}:00`, - duration_minutes: Number(duracao), - notes, - appointment_type: tipoConsulta, - }; - await appointmentsService.create(body); - const dateFormatted = selectedDate.split("-").reverse().join("/"); - toast({ - title: "Consulta agendada!", - description: `Consulta marcada para ${dateFormatted} às ${selectedTime} com o(a) médico(a) ${ - doctors.find((d) => d.id === selectedDoctor)?.full_name || "" - }.`, - }); - setSelectedDoctor(""); - setSelectedDate(""); - setSelectedTime(""); - setNotes(""); - setSelectedPatient(""); - } catch (err) { - console.error(err); - toast({ title: "Erro", description: "Falha ao agendar consulta." }); - } - }; // 🔹 Tooltip no calendário useEffect(() => { diff --git a/services/Sms.mjs b/services/Sms.mjs new file mode 100644 index 0000000..daf4984 --- /dev/null +++ b/services/Sms.mjs @@ -0,0 +1,58 @@ +/** + * Serviço de SMS via Supabase Edge Function (sem backend) + * Usa o token JWT salvo no localStorage (chave: "token") + */ + +const SUPABASE_FUNCTION_URL = + "https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/send-sms"; + +export const smsService = { + /** + * Envia um SMS de lembrete via Twilio + * @param {Object} params + * @param {string} params.phone_number - Ex: +5511999999999 + * @param {string} params.message - Mensagem de texto + * @param {string} [params.patient_id] - ID opcional do paciente + */ + async sendSms({ phone_number, message, patient_id }) { + try { + // 🔹 Busca o token salvo pelo login + const token = localStorage.getItem("token"); + + if (!token) { + console.error("❌ Nenhum token JWT encontrado no localStorage (chave: 'token')."); + return { success: false, error: "Token JWT não encontrado." }; + } + + const body = JSON.stringify({ + phone_number, + message, + patient_id, + }); + + console.log("[smsService] Enviando SMS para:", phone_number); + + const response = await fetch(SUPABASE_FUNCTION_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, // 🔑 autenticação Supabase + }, + body, + }); + + const result = await response.json(); + + if (!response.ok) { + console.error("❌ Falha no envio do SMS:", result); + return { success: false, error: result }; + } + + console.log("✅ SMS enviado com sucesso:", result); + return result; + } catch (err) { + console.error("❌ Erro inesperado ao enviar SMS:", err); + return { success: false, error: err.message }; + } + }, +}; diff --git a/services/api.mjs b/services/api.mjs index c639488..c9d6e61 100644 --- a/services/api.mjs +++ b/services/api.mjs @@ -89,11 +89,20 @@ async function request(endpoint, options = {}) { // --- CORREÇÃO 1: PARA O SUBMIT DO AGENDAMENTO --- // Se a resposta for um sucesso de criação (201) ou sem conteúdo (204), não quebra. - if (response.status === 201 || response.status === 204) { - return null; + // --- CORREÇÃO: funções do Supabase retornam 200 ou 201, nunca queremos perder o body --- + if (response.status === 204) { + return null; + } + + const text = await response.text(); + try { + return JSON.parse(text); + } catch { + return text || null; } - return response.json(); + + } // Exportamos o objeto 'api' com os métodos que os componentes vão usar. diff --git a/services/appointmentsApi.mjs b/services/appointmentsApi.mjs index 5180c6e..f5a5f7b 100644 --- a/services/appointmentsApi.mjs +++ b/services/appointmentsApi.mjs @@ -45,4 +45,7 @@ export const appointmentsService = { * @returns {Promise} - Uma promessa que resolve com a resposta da API. */ delete: (id) => api.delete(`/rest/v1/appointments?id=eq.${id}`), + + + }; \ No newline at end of file