From c41d561dd6e3a82f77f8032a71bbbc78319e0fbd Mon Sep 17 00:00:00 2001 From: m1guelmcf Date: Wed, 26 Nov 2025 11:14:25 -0300 Subject: [PATCH] medicos ordem alfabetica --- components/schedule/schedule-form.tsx | 394 +++++++++++++++----------- 1 file changed, 231 insertions(+), 163 deletions(-) diff --git a/components/schedule/schedule-form.tsx b/components/schedule/schedule-form.tsx index 89ffb05..257267e 100644 --- a/components/schedule/schedule-form.tsx +++ b/components/schedule/schedule-form.tsx @@ -9,15 +9,20 @@ import { AvailabilityService } from "@/services/availabilityApi.mjs"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; 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 { 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"); @@ -39,17 +44,32 @@ export default function ScheduleForm() { const [tipoConsulta] = useState("presencial"); const [duracao] = useState("30"); const [disponibilidades, setDisponibilidades] = useState([]); - const [availabilityCounts, setAvailabilityCounts] = useState>({}); - const [tooltip, setTooltip] = useState<{ x: number; y: number; text: string } | null>(null); + const [availabilityCounts, setAvailabilityCounts] = useState< + Record + >({}); + const [tooltip, setTooltip] = useState<{ + x: number; + y: number; + text: string; + } | null>(null); const calendarRef = useRef(null); // Funções auxiliares const getWeekdayNumber = (weekday: string) => - ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] - .indexOf(weekday.toLowerCase()) + 1; + [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + ].indexOf(weekday.toLowerCase()) + 1; const getBrazilDate = (date: Date) => - new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0)); + new Date( + Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0) + ); // 🔹 Buscar dados do usuário e role useEffect(() => { @@ -78,7 +98,10 @@ export default function ScheduleForm() { setDoctors(data || []); } catch (err) { console.error("Erro ao buscar médicos:", err); - toast({ title: "Erro", description: "Não foi possível carregar médicos." }); + toast({ + title: "Erro", + description: "Não foi possível carregar médicos.", + }); } finally { setLoadingDoctors(false); } @@ -101,7 +124,10 @@ export default function ScheduleForm() { } }, []); - const computeAvailabilityCountsPreview = async (doctorId: string, dispList: any[]) => { + const computeAvailabilityCountsPreview = async ( + doctorId: string, + dispList: any[] + ) => { try { const today = new Date(); const start = format(today, "yyyy-MM-dd"); @@ -123,7 +149,9 @@ export default function ScheduleForm() { const d = addDays(today, i); const key = format(d, "yyyy-MM-dd"); const dayOfWeek = d.getDay() === 0 ? 7 : d.getDay(); - const dailyDisp = dispList.filter((p) => getWeekdayNumber(p.weekday) === dayOfWeek); + const dailyDisp = dispList.filter( + (p) => getWeekdayNumber(p.weekday) === dayOfWeek + ); if (dailyDisp.length === 0) { counts[key] = 0; continue; @@ -135,7 +163,8 @@ export default function ScheduleForm() { const startMin = sh * 60 + sm; const endMin = eh * 60 + em; const slot = p.slot_minutes || 30; - if (endMin >= startMin) possible += Math.floor((endMin - startMin) / slot) + 1; + if (endMin >= startMin) + possible += Math.floor((endMin - startMin) / slot) + 1; }); const occupied = apptsByDate[key] || 0; counts[key] = Math.max(0, possible - occupied); @@ -161,166 +190,175 @@ export default function ScheduleForm() { }, [selectedDoctor, loadDoctorDisponibilidades]); // 🔹 Buscar horários disponíveis - const fetchAvailableSlots = useCallback(async (doctorId: string, date: string) => { - if (!doctorId || !date) return; - setLoadingSlots(true); - setAvailableTimes([]); - try { - const disponibilidades = await AvailabilityService.listById(doctorId); - const consultas = await appointmentsService.search_appointment( - `doctor_id=eq.${doctorId}&scheduled_at=gte.${date}T00:00:00Z&scheduled_at=lt.${date}T23:59:59Z` - ); - const diaJS = new Date(date).getDay(); - const diaAPI = diaJS === 0 ? 7 : diaJS; - const disponibilidadeDia = disponibilidades.find( - (d: any) => getWeekdayNumber(d.weekday) === diaAPI - ); - if (!disponibilidadeDia) { - toast({ title: "Nenhuma disponibilidade", description: "Nenhum horário para este dia." }); - return setAvailableTimes([]); + const fetchAvailableSlots = useCallback( + async (doctorId: string, date: string) => { + if (!doctorId || !date) return; + setLoadingSlots(true); + setAvailableTimes([]); + try { + const disponibilidades = await AvailabilityService.listById(doctorId); + const consultas = await appointmentsService.search_appointment( + `doctor_id=eq.${doctorId}&scheduled_at=gte.${date}T00:00:00Z&scheduled_at=lt.${date}T23:59:59Z` + ); + const diaJS = new Date(date).getDay(); + const diaAPI = diaJS === 0 ? 7 : diaJS; + const disponibilidadeDia = disponibilidades.find( + (d: any) => getWeekdayNumber(d.weekday) === diaAPI + ); + if (!disponibilidadeDia) { + toast({ + title: "Nenhuma disponibilidade", + description: "Nenhum horário para este dia.", + }); + return setAvailableTimes([]); + } + const [startHour, startMin] = disponibilidadeDia.start_time + .split(":") + .map(Number); + const [endHour, endMin] = disponibilidadeDia.end_time + .split(":") + .map(Number); + const slot = disponibilidadeDia.slot_minutes || 30; + const horariosGerados: string[] = []; + let atual = new Date(date); + atual.setHours(startHour, startMin, 0, 0); + const end = new Date(date); + end.setHours(endHour, endMin, 0, 0); + while (atual <= end) { + horariosGerados.push(atual.toTimeString().slice(0, 5)); + atual = new Date(atual.getTime() + slot * 60000); + } + const ocupados = (consultas || []).map((c: any) => + String(c.scheduled_at).split("T")[1]?.slice(0, 5) + ); + const livres = horariosGerados.filter((h) => !ocupados.includes(h)); + setAvailableTimes(livres); + } catch (err) { + console.error(err); + toast({ title: "Erro", description: "Falha ao carregar horários." }); + } finally { + setLoadingSlots(false); } - const [startHour, startMin] = disponibilidadeDia.start_time.split(":").map(Number); - const [endHour, endMin] = disponibilidadeDia.end_time.split(":").map(Number); - const slot = disponibilidadeDia.slot_minutes || 30; - const horariosGerados: string[] = []; - let atual = new Date(date); - atual.setHours(startHour, startMin, 0, 0); - const end = new Date(date); - end.setHours(endHour, endMin, 0, 0); - while (atual <= end) { - horariosGerados.push(atual.toTimeString().slice(0, 5)); - atual = new Date(atual.getTime() + slot * 60000); - } - const ocupados = (consultas || []).map((c: any) => - String(c.scheduled_at).split("T")[1]?.slice(0, 5) - ); - const livres = horariosGerados.filter((h) => !ocupados.includes(h)); - setAvailableTimes(livres); - } catch (err) { - console.error(err); - toast({ title: "Erro", description: "Falha ao carregar horários." }); - } finally { - setLoadingSlots(false); - } - }, []); + }, + [] + ); useEffect(() => { - if (selectedDoctor && selectedDate) fetchAvailableSlots(selectedDoctor, selectedDate); + if (selectedDoctor && selectedDate) + fetchAvailableSlots(selectedDoctor, selectedDate); }, [selectedDoctor, selectedDate, fetchAvailableSlots]); // 🔹 Submeter agendamento // 🔹 Submeter agendamento - // 🔹 Submeter agendamento -// 🔹 Submeter agendamento -// 🔹 Submeter agendamento -const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); + // 🔹 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; - } + 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, - }; + 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); + // ✅ mantém o fluxo original de criação (funcional) + await appointmentsService.create(body); - const dateFormatted = selectedDate.split("-").reverse().join("/"); + 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 || "" - }.`, - }); + 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 + let phoneNumber = "+5511999999999"; // fallback -try { - if (isSecretaryLike) { - // Secretária/admin → telefone do paciente selecionado - const patient = patients.find((p: any) => p.id === patientId); + 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; + // 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(); + 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; + 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; - } + 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." }); - } -}; + // 🔹 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." }); + } + }; // 🔹 Tooltip no calendário useEffect(() => { @@ -359,19 +397,27 @@ try { Dados da Consulta -
+
{/* Se secretária/gestor/admin → mostrar campo Paciente */} {["secretaria", "gestor", "admin"].includes(role) && (
- {patients.map((p) => ( - {p.full_name} + + {p.full_name} + ))} @@ -380,19 +426,28 @@ try {
- {loadingDoctors ? ( - Carregando... + + Carregando... + ) : ( - doctors.map((d) => ( - - {d.full_name} — {d.specialty} - - )) + [...doctors] + .sort((a, b) => + String(a.full_name).localeCompare(String(b.full_name)) + ) + .map((d) => ( + + {d.full_name} — {d.specialty} + + )) )} @@ -404,10 +459,17 @@ try { { if (!date) return; - const formatted = format(new Date(date.getTime() + 12 * 60 * 60 * 1000), "yyyy-MM-dd"); + const formatted = format( + new Date(date.getTime() + 12 * 60 * 60 * 1000), + "yyyy-MM-dd" + ); setSelectedDate(formatted); }} /> @@ -436,7 +498,8 @@ try {
{selectedDoctor - ? doctors.find((d) => d.id === selectedDoctor)?.full_name + ? doctors.find((d) => d.id === selectedDoctor) + ?.full_name : "Médico"}
@@ -447,7 +510,10 @@ try {
- {availableTimes.map((h) => ( - {h} + + {h} + ))}