"use client"; import { useState, useEffect, useCallback, useRef } from "react"; import { usersService } from "@/services/usersApi.mjs"; import { patientsService } from "@/services/patientsApi.mjs"; import { doctorsService } from "@/services/doctorsApi.mjs"; import { appointmentsService } from "@/services/appointmentsApi.mjs"; 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 { Textarea } from "@/components/ui/textarea"; import { Calendar as CalendarShadcn } from "@/components/ui/calendar"; import { format, addDays } from "date-fns"; import { User, StickyNote } from "lucide-react"; import { toast } from "@/hooks/use-toast"; import {api} from "@/services/api.mjs" export default function ScheduleForm() { // Estado do usuário e role const [role, setRole] = useState("paciente"); const [userId, setUserId] = useState(null); // Listas e seleções const [patients, setPatients] = useState([]); const [selectedPatient, setSelectedPatient] = useState(""); const [doctors, setDoctors] = useState([]); const [selectedDoctor, setSelectedDoctor] = useState(""); const [selectedDate, setSelectedDate] = useState(""); const [selectedTime, setSelectedTime] = useState(""); const [notes, setNotes] = useState(""); const [availableTimes, setAvailableTimes] = useState([]); const [loadingDoctors, setLoadingDoctors] = useState(true); const [loadingSlots, setLoadingSlots] = useState(false); // Outras configs 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 calendarRef = useRef(null); const getWeekdayNumber = (weekday: string) => ["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)); // 🔹 Buscar dados do usuário e role useEffect(() => { (async () => { try { const me = await usersService.getMe(); const currentRole = me?.roles?.[0] || "paciente"; setRole(currentRole); setUserId(me?.user?.id || null); if (["secretaria", "gestor", "admin"].includes(currentRole)) { const pats = await patientsService.list(); setPatients(pats || []); } } catch (err) { console.error("Erro ao carregar usuário:", err); } })(); }, []); // 🔹 Buscar médicos const fetchDoctors = useCallback(async () => { setLoadingDoctors(true); try { const data = await doctorsService.list(); setDoctors(data || []); } catch (err) { console.error("Erro ao buscar médicos:", err); toast({ title: "Erro", description: "Não foi possível carregar médicos." }); } finally { setLoadingDoctors(false); } }, []); useEffect(() => { fetchDoctors(); }, [fetchDoctors]); // 🔹 Buscar disponibilidades const loadDoctorDisponibilidades = useCallback(async (doctorId?: string) => { if (!doctorId) return; try { const disp = await AvailabilityService.listById(doctorId); setDisponibilidades(disp || []); await computeAvailabilityCountsPreview(doctorId, disp || []); } catch (err) { console.error("Erro ao buscar disponibilidades:", err); setDisponibilidades([]); } }, []); const computeAvailabilityCountsPreview = async (doctorId: string, dispList: any[]) => { try { const today = new Date(); const start = format(today, "yyyy-MM-dd"); const endDate = addDays(today, 90); const end = format(endDate, "yyyy-MM-dd"); const appointments = await appointmentsService.search_appointment( `doctor_id=eq.${doctorId}&scheduled_at=gte.${start}T00:00:00Z&scheduled_at=lt.${end}T23:59:59Z` ); const apptsByDate: Record = {}; (appointments || []).forEach((a: any) => { const d = String(a.scheduled_at).split("T")[0]; apptsByDate[d] = (apptsByDate[d] || 0) + 1; }); const counts: Record = {}; for (let i = 0; i <= 90; i++) { 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); if (dailyDisp.length === 0) { counts[key] = 0; continue; } let possible = 0; dailyDisp.forEach((p) => { const [sh, sm] = p.start_time.split(":").map(Number); const [eh, em] = p.end_time.split(":").map(Number); 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; }); const occupied = apptsByDate[key] || 0; counts[key] = Math.max(0, possible - occupied); } setAvailabilityCounts(counts); } catch (err) { console.error("Erro ao calcular contagens:", err); setAvailabilityCounts({}); } }; // 🔹 Quando médico muda useEffect(() => { if (selectedDoctor) { loadDoctorDisponibilidades(selectedDoctor); } else { setDisponibilidades([]); setAvailabilityCounts({}); } setSelectedDate(""); setSelectedTime(""); setAvailableTimes([]); }, [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 [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); }, [selectedDoctor, selectedDate, fetchAvailableSlots]); // 🔹 Submeter agendamento // 🔹 Submeter agendamento const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const isSecretaryLike = ["secretaria", "admin", "gestor"].includes(role); let patientId = selectedPatient; try { // 🔹 Se for paciente, buscamos o ID real na tabela `patients` if (!isSecretaryLike) { const me = await usersService.getMe(); const authId = me?.user?.id; if (!authId) { toast({ title: "Erro", description: "Usuário não autenticado." }); return; } // Busca o registro de paciente correspondente ao usuário autenticado const patientsData = await api.get(`/rest/v1/patients?user_id=eq.${authId}`); if (!patientsData || patientsData.length === 0) { toast({ title: "Erro", description: "Registro de paciente não encontrado." }); return; } patientId = patientsData[0].id; } if (!patientId || !selectedDoctor || !selectedDate || !selectedTime) { toast({ title: "Campos obrigatórios", description: "Preencha todos os campos." }); return; } const body = { doctor_id: selectedDoctor, patient_id: patientId, scheduled_at: `${selectedDate}T${selectedTime}:00Z`, duration_minutes: 30, notes, appointment_type: "presencial", created_by: userId, }; console.log("🩵 Enviando agendamento:", body); try { await appointmentsService.create(body); toast({ title: "Sucesso", description: "Consulta agendada com sucesso!" }); } catch (err) { console.warn("⚠️ Tentando método alternativo..."); await appointmentsService.create?.(body); } setSelectedDoctor(""); setSelectedDate(""); setSelectedTime(""); setNotes(""); setSelectedPatient(""); } catch (err) { console.error("❌ Erro ao agendar:", err); toast({ title: "Erro", description: "Falha ao agendar consulta." }); } }; // 🔹 Tooltip no calendário useEffect(() => { const cont = calendarRef.current; if (!cont) return; const onMove = (ev: MouseEvent) => { const target = ev.target as HTMLElement | null; const btn = target?.closest("button"); if (!btn) return setTooltip(null); const aria = btn.getAttribute("aria-label") || btn.textContent || ""; const parsed = new Date(aria); if (isNaN(parsed.getTime())) return setTooltip(null); const key = format(getBrazilDate(parsed), "yyyy-MM-dd"); const count = availabilityCounts[key] ?? 0; setTooltip({ x: ev.pageX + 10, y: ev.pageY + 10, text: `${count} horário${count !== 1 ? "s" : ""} disponíveis`, }); }; const onLeave = () => setTooltip(null); cont.addEventListener("mousemove", onMove); cont.addEventListener("mouseleave", onLeave); return () => { cont.removeEventListener("mousemove", onMove); cont.removeEventListener("mouseleave", onLeave); }; }, [availabilityCounts]); return (

Agendar Consulta

Dados da Consulta
{/* Se secretária/gestor/admin → mostrar campo Paciente */} {["secretaria", "gestor", "admin"].includes(role) && (
)}
{ if (!date) return; const formatted = format(new Date(date.getTime() + 12 * 60 * 60 * 1000), "yyyy-MM-dd"); setSelectedDate(formatted); }} />