From 3443e46ca35a2b59161c71262fcefe279420643d Mon Sep 17 00:00:00 2001 From: guisilvagomes Date: Wed, 5 Nov 2025 16:51:33 -0300 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20implementa=20chatbot=20AI,=20gerenc?= =?UTF-8?q?iamento=20de=20disponibilidade=20m=C3=A9dica,=20visualiza=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20laudos=20e=20melhorias=20no=20painel=20da=20secr?= =?UTF-8?q?et=C3=A1ria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adiciona chatbot AI com interface responsiva e posicionamento otimizado - Implementa gerenciamento completo de disponibilidade e exceções médicas - Adiciona modal de visualização detalhada de laudos no painel do paciente - Corrige relatórios da secretária para mostrar nomes de médicos - Implementa mensagem de boas-vindas personalizada com nome real - Remove mensagens duplicadas de login - Remove arquivo cleanup-deps.ps1 desnecessário - Atualiza README com todas as novas funcionalidades --- AGENDAMENTO-SLOTS-API.md | 294 ------ README.md | 53 +- cleanup-deps.ps1 | 20 - src/components/AgendamentoConsulta.tsx | 512 ++++++++--- src/components/Chatbot.tsx | 293 +++--- src/components/DisponibilidadeMedico.old.tsx | 737 +++++++++++++++ src/components/DisponibilidadeMedico.tsx | 869 ++++++++---------- .../agenda/AvailableSlotsPicker.tsx | 106 ++- src/components/agenda/CalendarPicker.tsx | 343 +++++++ .../agenda/ScheduleAppointmentModal.tsx | 12 +- src/components/consultas/ConsultaModal.tsx | 137 +-- src/components/pacientes/PacienteForm.tsx | 54 +- src/components/secretaria/AgendaSection.tsx | 2 + .../secretaria/ConsultasSection.tsx | 2 + .../secretaria/RelatoriosSection.tsx | 2 + .../secretaria/SecretaryAppointmentList.tsx | 73 +- .../secretaria/SecretaryDoctorList.tsx | 48 +- .../secretaria/SecretaryDoctorSchedule.tsx | 68 +- .../secretaria/SecretaryPatientList.tsx | 2 + .../secretaria/SecretaryReportList.tsx | 70 +- src/components/ui/AvatarUpload.tsx | 2 + src/components/ui/ConfirmDialog.tsx | 4 +- src/context/AuthContext.tsx | 2 - src/index.css | 54 ++ src/pages/AcompanhamentoPaciente.tsx | 416 +++++++-- src/pages/AgendamentoPaciente.tsx | 243 ++--- src/pages/AuthCallback.tsx | 1 - src/pages/GerenciarUsuarios.tsx | 20 +- src/pages/Home.tsx | 20 +- src/pages/ListaMedicos.tsx | 139 +-- src/pages/ListaPacientes.tsx | 121 ++- src/pages/ListaSecretarias.tsx | 84 +- src/pages/LoginMedico.tsx | 19 +- src/pages/LoginPaciente.tsx | 19 +- src/pages/LoginSecretaria.tsx | 19 +- src/pages/PainelAdmin.tsx | 374 ++++---- src/pages/PainelMedico.tsx | 662 +++++++++++-- src/pages/PainelSecretaria.backup.tsx | 84 +- src/pages/PainelSecretaria.tsx | 37 +- src/pages/PerfilMedico.tsx | 143 ++- src/pages/PerfilPaciente.tsx | 92 +- src/services/api/client.ts | 14 +- .../appointments/appointmentService.ts | 11 +- .../availability/availabilityService.ts | 35 +- src/services/availability/types.ts | 11 +- src/services/doctors/doctorService.ts | 36 + tailwind.config.js | 33 + 47 files changed, 4393 insertions(+), 1999 deletions(-) delete mode 100644 AGENDAMENTO-SLOTS-API.md delete mode 100644 cleanup-deps.ps1 create mode 100644 src/components/DisponibilidadeMedico.old.tsx create mode 100644 src/components/agenda/CalendarPicker.tsx diff --git a/AGENDAMENTO-SLOTS-API.md b/AGENDAMENTO-SLOTS-API.md deleted file mode 100644 index 73854d0c8..000000000 --- a/AGENDAMENTO-SLOTS-API.md +++ /dev/null @@ -1,294 +0,0 @@ -# Sistema de Agendamento com API de Slots - -## Implementação Concluída ✅ - -### Fluxo de Agendamento - -1. **Usuário seleciona médico** → Mostra calendário -2. **Usuário seleciona data** → Chama API de slots disponíveis -3. **API calcula horários** → Considera: - - Disponibilidade do médico (agenda configurada) - - Exceções (bloqueios e horários extras) - - Antecedência mínima para agendamento - - Consultas já agendadas -4. **Usuário seleciona horário** e preenche motivo -5. **Sistema cria agendamento** → Salva no banco - ---- - -## APIs Implementadas - -### 1. Calcular Slots Disponíveis - -**Endpoint**: `POST /functions/v1/get-available-slots` - -**Request**: - -```json -{ - "doctor_id": "uuid-do-medico", - "date": "2025-10-30" -} -``` - -**Response**: - -```json -{ - "slots": [ - { - "time": "09:00", - "available": true - }, - { - "time": "09:30", - "available": false - }, - { - "time": "10:00", - "available": true - } - ] -} -``` - -**Código Implementado**: - -```typescript -// src/services/appointments/appointmentService.ts -async getAvailableSlots(data: GetAvailableSlotsInput): Promise { - const response = await apiClient.post( - "/functions/v1/get-available-slots", - data - ); - return response.data; -} -``` - ---- - -### 2. Criar Agendamento - -**Endpoint**: `POST /rest/v1/appointments` - -**Request**: - -```json -{ - "doctor_id": "uuid-do-medico", - "patient_id": "uuid-do-paciente", - "scheduled_at": "2025-10-30T09:00:00Z", - "duration_minutes": 30, - "appointment_type": "presencial", - "chief_complaint": "Consulta de rotina", - "created_by": "uuid-do-usuario" -} -``` - -**Response**: - -```json -{ - "id": "uuid-do-agendamento", - "order_number": "APT-2025-0001", - "status": "requested", - ... -} -``` - -**Código Implementado**: - -```typescript -// src/services/appointments/appointmentService.ts -async create(data: CreateAppointmentInput): Promise { - const payload = { - ...data, - duration_minutes: data.duration_minutes || 30, - appointment_type: data.appointment_type || "presencial", - status: "requested", - }; - - const response = await apiClient.post( - "/rest/v1/appointments", - payload, - { - headers: { - Prefer: "return=representation", - }, - } - ); - - return response.data[0]; -} -``` - ---- - -## Componente AgendamentoConsulta - -### Principais Melhorias - -#### Antes ❌ - -- Calculava slots manualmente no frontend -- Precisava carregar disponibilidade + exceções separadamente -- Lógica complexa de validação no cliente -- Não considerava antecedência mínima -- Não verificava consultas já agendadas - -#### Depois ✅ - -- Usa Edge Function para calcular slots -- API retorna apenas horários realmente disponíveis -- Validações centralizadas no backend -- Considera todas as regras de negócio -- Performance melhorada (menos requisições) - -### Código Simplificado - -```typescript -// src/components/AgendamentoConsulta.tsx - -const calculateAvailableSlots = useCallback(async () => { - if (!selectedDate || !selectedMedico) { - setAvailableSlots([]); - return; - } - - try { - const dateStr = format(selectedDate, "yyyy-MM-dd"); - - // Chama a Edge Function - const response = await appointmentService.getAvailableSlots({ - doctor_id: selectedMedico.id, - date: dateStr, - }); - - if (response && response.slots) { - // Filtra apenas slots disponíveis - const available = response.slots - .filter((slot) => slot.available) - .map((slot) => slot.time); - setAvailableSlots(available); - } else { - setAvailableSlots([]); - } - } catch (error) { - console.error("Erro ao buscar slots:", error); - setAvailableSlots([]); - } -}, [selectedDate, selectedMedico]); - -const confirmAppointment = async () => { - if (!selectedMedico || !selectedDate || !selectedTime || !user) return; - - try { - const scheduledAt = - format(selectedDate, "yyyy-MM-dd") + "T" + selectedTime + ":00Z"; - - // Cria o agendamento - const appointment = await appointmentService.create({ - patient_id: user.id, - doctor_id: selectedMedico.id, - scheduled_at: scheduledAt, - duration_minutes: 30, - appointment_type: - appointmentType === "online" ? "telemedicina" : "presencial", - chief_complaint: motivo, - }); - - console.log("Consulta criada:", appointment); - setBookingSuccess(true); - } catch (error) { - setBookingError(error.message); - } -}; -``` - ---- - -## Tipos TypeScript - -```typescript -// src/services/appointments/types.ts - -export interface GetAvailableSlotsInput { - doctor_id: string; - date: string; // YYYY-MM-DD -} - -export interface TimeSlot { - time: string; // HH:MM (ex: "09:00") - available: boolean; -} - -export interface GetAvailableSlotsResponse { - slots: TimeSlot[]; -} - -export interface CreateAppointmentInput { - patient_id: string; - doctor_id: string; - scheduled_at: string; // ISO 8601 - duration_minutes?: number; - appointment_type?: "presencial" | "telemedicina"; - chief_complaint?: string; - patient_notes?: string; - insurance_provider?: string; -} -``` - ---- - -## Benefícios da Implementação - -✅ **Performance**: Menos requisições ao backend -✅ **Confiabilidade**: Validações centralizadas -✅ **Manutenibilidade**: Lógica de negócio no servidor -✅ **Escalabilidade**: Edge Functions são otimizadas -✅ **UX**: Interface mais responsiva e clara -✅ **Segurança**: Validações no backend não podem ser burladas - ---- - -## Próximos Passos (Opcional) - -- [ ] Adicionar loading states mais detalhados -- [ ] Implementar cache de slots (evitar chamadas repetidas) -- [ ] Adicionar retry automático em caso de falha -- [ ] Mostrar motivo quando slot não está disponível -- [ ] Implementar notificações (SMS/Email) após agendamento - ---- - -## Testando - -### 1. Selecione um médico - -### 2. Selecione uma data futura - -### 3. Verifique os slots disponíveis - -### 4. Selecione um horário - -### 5. Preencha o motivo - -### 6. Confirme o agendamento - -**Logs no Console**: - -``` -[AppointmentService] Buscando slots para: {doctor_id, date} -[AppointmentService] Slots recebidos: 12 slots -[AppointmentService] Criando agendamento... -[AppointmentService] Consulta criada: {id, order_number, ...} -``` - ---- - -## Data de Implementação - -**30 de Outubro de 2025** - -Implementado por: GitHub Copilot -Revisado por: Equipe RiseUp Squad 18 diff --git a/README.md b/README.md index 7e2538980..859e9b1e3 100644 --- a/README.md +++ b/README.md @@ -83,27 +83,33 @@ pnpm wrangler pages deploy dist --project-name=mediconnect --branch=production ### 🏥 Para Médicos - ✅ Agenda personalizada com disponibilidade configurável -- ✅ Gerenciamento de exceções (bloqueios e horários extras) +- ✅ Gerenciamento completo de disponibilidade semanal +- ✅ Sistema de exceções (bloqueios e horários extras) - ✅ Prontuário eletrônico completo - ✅ Histórico de consultas do paciente - ✅ Dashboard com métricas e estatísticas - ✅ Teleconsulta e presencial +- ✅ Chatbot AI para suporte ### 👥 Para Pacientes - ✅ Agendamento inteligente com slots disponíveis em tempo real - ✅ Histórico completo de consultas +- ✅ Visualização detalhada de laudos médicos com modal - ✅ Visualização e download de relatórios médicos (PDF) - ✅ Perfil com avatar e dados pessoais - ✅ Filtros por médico, especialidade e data +- ✅ Chatbot AI para dúvidas e suporte ### 🏢 Para Secretárias - ✅ Gerenciamento completo de médicos, pacientes e consultas - ✅ Cadastro com validação de CPF e CRM - ✅ Configuração de agenda médica (horários e exceções) +- ✅ Relatórios com nomes de médicos (não apenas IDs) - ✅ Busca e filtros avançados - ✅ Confirmação profissional para exclusões +- ✅ Boas-vindas personalizadas com nome real ### 🔐 Sistema de Autenticação @@ -312,7 +318,38 @@ PATCH /rest/v1/doctors?id=eq.{uuid} --- -## 🚀 Melhorias Recentes (Outubro 2025) +## 🚀 Melhorias Recentes (Novembro 2025) + +### Chatbot AI 🤖 + +- ✅ Assistente virtual inteligente com IA +- ✅ Interface de chat moderna e responsiva +- ✅ Posicionamento otimizado (canto inferior esquerdo) +- ✅ Respostas personalizadas sobre o sistema +- ✅ Suporte a dúvidas sobre agendamento e funcionalidades + +### Gerenciamento de Disponibilidade Médica 📅 + +- ✅ Painel completo de disponibilidade no painel do médico +- ✅ Criação e edição de horários semanais +- ✅ Sistema de exceções (bloqueios e horários extras) +- ✅ Visualização em abas (Horário Semanal e Exceções) +- ✅ Interface intuitiva com validações completas + +### Visualização de Laudos 🔍 + +- ✅ Botão de visualização (ícone de olho) no painel do paciente +- ✅ Modal detalhado com informações completas do laudo +- ✅ Exibição de: número do pedido, status, exame, diagnóstico, CID, conclusão +- ✅ Suporte a modo escuro +- ✅ Formatação de datas em português + +### Melhorias no Painel da Secretária 👩‍💼 + +- ✅ Relatórios mostram nome do médico ao invés de ID +- ✅ Mensagem de boas-vindas personalizada com nome real +- ✅ Busca e resolução automática de nomes de médicos +- ✅ Fallback para email caso nome não esteja disponível ### Sistema de Agendamento @@ -322,15 +359,10 @@ PATCH /rest/v1/doctors?id=eq.{uuid} - ✅ Verificação de conflitos - ✅ Interface otimizada -### Formatação de Dados - -- ✅ Limpeza automática de telefone/CPF -- ✅ Formatação de nomes de médicos ("Dr.") -- ✅ Validação de campos obrigatórios -- ✅ Máscaras de entrada - ### UX/UI +- ✅ Toast único de boas-vindas após login (removidas mensagens duplicadas) +- ✅ Chatbot responsivo adaptado ao tamanho da tela - ✅ Diálogos de confirmação profissionais - ✅ Filtros de busca em todas as listas - ✅ Feedback visual melhorado @@ -339,10 +371,11 @@ PATCH /rest/v1/doctors?id=eq.{uuid} ### Performance -- ✅ Build otimizado (~424KB) +- ✅ Build otimizado (~467KB) - ✅ Code splitting - ✅ Lazy loading de rotas - ✅ Cache de assets +- ✅ Remoção de dependências não utilizadas --- diff --git a/cleanup-deps.ps1 b/cleanup-deps.ps1 deleted file mode 100644 index f9db9298a..000000000 --- a/cleanup-deps.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -# Script de limpeza de dependências não utilizadas -# Execute este arquivo no PowerShell - -Write-Host "🧹 Limpando dependências não utilizadas..." -ForegroundColor Cyan - -# Remover pacotes não utilizados -Write-Host "`n📦 Removendo @lumi.new/sdk..." -ForegroundColor Yellow -pnpm remove @lumi.new/sdk - -Write-Host "`n📦 Removendo node-fetch..." -ForegroundColor Yellow -pnpm remove node-fetch - -Write-Host "`n📦 Removendo react-toastify..." -ForegroundColor Yellow -pnpm remove react-toastify - -Write-Host "`n✅ Limpeza concluída!" -ForegroundColor Green -Write-Host "📊 Verificando tamanho de node_modules..." -ForegroundColor Cyan - -$size = (Get-ChildItem "node_modules" -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum / 1MB -Write-Host "Tamanho atual: $([math]::Round($size, 2)) MB" -ForegroundColor White diff --git a/src/components/AgendamentoConsulta.tsx b/src/components/AgendamentoConsulta.tsx index 0421548e7..e8c7557e9 100644 --- a/src/components/AgendamentoConsulta.tsx +++ b/src/components/AgendamentoConsulta.tsx @@ -1,4 +1,5 @@ -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, useRef } from "react"; +import { useNavigate } from "react-router-dom"; import { format, addMonths, @@ -8,7 +9,6 @@ import { eachDayOfInterval, isSameMonth, isSameDay, - isToday, isBefore, startOfDay, } from "date-fns"; @@ -45,7 +45,9 @@ export default function AgendamentoConsulta({ medicos, }: AgendamentoConsultaProps) { const { user } = useAuth(); + const navigate = useNavigate(); const [filteredMedicos, setFilteredMedicos] = useState(medicos); + const detailsRef = useRef(null); // Sempre que a lista de médicos da API mudar, atualiza o filtro useEffect(() => { @@ -65,6 +67,9 @@ export default function AgendamentoConsulta({ const [showConfirmDialog, setShowConfirmDialog] = useState(false); const [bookingSuccess, setBookingSuccess] = useState(false); const [bookingError, setBookingError] = useState(""); + const [showResultModal, setShowResultModal] = useState(false); + const [resultType, setResultType] = useState<'success' | 'error'>('success'); + const [availableDates, setAvailableDates] = useState>(new Set()); // Removido o carregamento interno de médicos, pois agora vem por prop @@ -87,6 +92,106 @@ export default function AgendamentoConsulta({ const specialties = Array.from(new Set(medicos.map((m) => m.especialidade))); + // Busca as disponibilidades do médico e calcula as datas disponíveis + useEffect(() => { + const loadAvailableDates = async () => { + if (!selectedMedico) { + setAvailableDates(new Set()); + return; + } + + try { + const { availabilityService } = await import("../services"); + + console.log("[AgendamentoConsulta] Buscando disponibilidades para médico:", { + id: selectedMedico.id, + nome: selectedMedico.nome + }); + + // Busca todas as disponibilidades ativas do médico + const availabilities = await availabilityService.list({ + doctor_id: selectedMedico.id, + active: true, + }); + + console.log("[AgendamentoConsulta] Disponibilidades retornadas da API:", { + count: availabilities?.length || 0, + data: availabilities + }); + + if (!availabilities || availabilities.length === 0) { + console.warn("[AgendamentoConsulta] Nenhuma disponibilidade encontrada para o médico"); + setAvailableDates(new Set()); + return; + } + + // Mapeamento de string para número (formato da API) + const weekdayMap: Record = { + "sunday": 0, + "monday": 1, + "tuesday": 2, + "wednesday": 3, + "thursday": 4, + "friday": 5, + "saturday": 6 + }; + + // Mapeia os dias da semana que o médico atende (converte para número) + const availableWeekdays = new Set( + availabilities.map((avail) => { + // weekday pode vir como número ou string da API + let weekdayNum: number; + + if (typeof avail.weekday === 'number') { + weekdayNum = avail.weekday; + } else if (typeof avail.weekday === 'string') { + weekdayNum = weekdayMap[avail.weekday.toLowerCase()] ?? -1; + } else { + weekdayNum = -1; + } + + console.log("[AgendamentoConsulta] Convertendo weekday:", { + original: avail.weekday, + type: typeof avail.weekday, + converted: weekdayNum + }); + return weekdayNum; + }).filter(day => day >= 0 && day <= 6) // Remove valores inválidos + ); + + console.log("[AgendamentoConsulta] Dias da semana disponíveis (números):", Array.from(availableWeekdays)); + + // Calcula todas as datas do mês atual e próximos 2 meses que têm disponibilidade + const today = startOfDay(new Date()); + const endDate = endOfMonth(addMonths(today, 2)); + const allDates = eachDayOfInterval({ start: today, end: endDate }); + + const availableDatesSet = new Set(); + + allDates.forEach((date) => { + const weekday = date.getDay(); + if (availableWeekdays.has(weekday) && !isBefore(date, today)) { + availableDatesSet.add(format(date, "yyyy-MM-dd")); + } + }); + + console.log("[AgendamentoConsulta] Resumo do cálculo:", { + weekdaysDisponiveis: Array.from(availableWeekdays), + datasCalculadas: availableDatesSet.size, + primeiras5Datas: Array.from(availableDatesSet).slice(0, 5) + }); + + setAvailableDates(availableDatesSet); + + } catch (error) { + console.error("[AgendamentoConsulta] Erro ao carregar disponibilidades:", error); + setAvailableDates(new Set()); + } + }; + + loadAvailableDates(); + }, [selectedMedico]); + // Removemos as funções de availability e exceptions antigas // A API de slots já considera tudo automaticamente @@ -219,16 +324,7 @@ export default function AgendamentoConsulta({ } else { setAvailableSlots([]); } - }, [selectedDate, selectedMedico, calculateAvailableSlots]); - - // Simplificado: a API de slots já considera disponibilidade e exceções - const isDateAvailable = (date: Date): boolean => { - // Não permite datas passadas - if (isBefore(date, startOfDay(new Date()))) return false; - // Para simplificar, consideramos todos os dias futuros como possíveis - // A API fará a validação real quando buscar slots - return true; - }; + }, [selectedDate, selectedMedico, appointmentType, calculateAvailableSlots]); const generateCalendarDays = () => { const start = startOfMonth(currentMonth); @@ -246,6 +342,22 @@ export default function AgendamentoConsulta({ const handlePrevMonth = () => setCurrentMonth(subMonths(currentMonth, 1)); const handleNextMonth = () => setCurrentMonth(addMonths(currentMonth, 1)); + + const handleMonthChange = (monthIndex: number) => { + const newDate = new Date(currentMonth); + newDate.setMonth(monthIndex); + setCurrentMonth(newDate); + }; + + const handleYearChange = (year: number) => { + const newDate = new Date(currentMonth); + newDate.setFullYear(year); + setCurrentMonth(newDate); + }; + + // Gera lista de anos (ano atual até +10 anos) + const availableYears = Array.from({ length: 11 }, (_, i) => new Date().getFullYear() + i); + const handleSelectDoctor = (medico: Medico) => { setSelectedMedico(medico); setSelectedDate(undefined); @@ -253,6 +365,14 @@ export default function AgendamentoConsulta({ setMotivo(""); setBookingSuccess(false); setBookingError(""); + + // Scroll suave para a seção de detalhes + setTimeout(() => { + detailsRef.current?.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + }, 100); }; const handleBookAppointment = () => { if (selectedMedico && selectedDate && selectedTime && motivo) { @@ -285,84 +405,137 @@ export default function AgendamentoConsulta({ console.log("[AgendamentoConsulta] Consulta criada com sucesso:", appointment); - setBookingSuccess(true); + // Mostra modal de sucesso + setResultType('success'); + setShowResultModal(true); setShowConfirmDialog(false); - - // Reset form após 3 segundos - setTimeout(() => { - setSelectedMedico(null); - setSelectedDate(undefined); - setSelectedTime(""); - setMotivo(""); - setBookingSuccess(false); - }, 3000); - } catch (error: any) { - console.error("[AgendamentoConsulta] Erro ao agendar:", { - error, - message: error?.message, - response: error?.response, - data: error?.response?.data, - }); - setBookingError( - error?.response?.data?.message || - error?.message || - "Erro ao agendar consulta. Tente novamente." - ); + setBookingSuccess(true); + } catch (error: unknown) { + console.error("[AgendamentoConsulta] Erro ao agendar:", error); + + const errorMessage = error instanceof Error + ? error.message + : "Erro ao agendar consulta. Tente novamente."; + + // Mostra modal de erro + setResultType('error'); + setShowResultModal(true); + setBookingError(errorMessage); setShowConfirmDialog(false); } }; const calendarDays = generateCalendarDays(); return ( -
- {bookingSuccess && ( -
- -
-

- Consulta agendada com sucesso! -

-

- Você receberá uma confirmação por e-mail em breve. -

+
+ {/* Modal de Resultado (Sucesso ou Erro) com Animação */} + {showResultModal && ( +
+
+
+ {/* Ícone com Animação Giratória (1 volta) */} +
+
+
+ {resultType === 'success' ? ( + + ) : ( + + )} +
+
+ + {/* Mensagem */} +
+

+ {resultType === 'success' ? 'Consulta Agendada!' : 'Erro no Agendamento'} +

+ {resultType === 'success' ? ( +
+

+ Sua consulta foi agendada com sucesso. Você receberá uma confirmação por e-mail ou SMS. +

+ +
+ ) : ( +

+ {bookingError || 'Não foi possível agendar a consulta. Tente novamente.'} +

+ )} +
+ + {/* Botão OK */} + +
)} - {bookingError && ( -
- -

{bookingError}

-
- )} +
-

Agendar Consulta

-

+

Agendar Consulta

+

Escolha um médico e horário disponível

-
-
+
+
-
- + handleMonthChange(Number(e.target.value))} + className="text-xs sm:text-sm font-semibold border border-gray-300 rounded-lg px-2 py-1 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500" + > + + + + + + + + + + + + + + + +
+
- {["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"].map( - (day) => ( + {["D", "S", "T", "Q", "Q", "S", "S"].map( + (day, idx) => (
{day}
@@ -489,32 +697,47 @@ export default function AgendamentoConsulta({ const isCurrentMonth = isSameMonth(day, currentMonth); const isSelected = selectedDate && isSameDay(day, selectedDate); - const isTodayDate = isToday(day); - const isAvailable = - isCurrentMonth && isDateAvailable(day); const isPast = isBefore(day, startOfDay(new Date())); + + // Verifica se a data está no conjunto de datas disponíveis + const dateStr = format(day, "yyyy-MM-dd"); + const isAvailable = isCurrentMonth && !isPast && availableDates.has(dateStr); + const isUnavailable = isCurrentMonth && !isPast && !availableDates.has(dateStr); + + // Debug apenas para o primeiro dia do mês atual + if (index === 0 && isCurrentMonth) { + console.log("[AgendamentoConsulta] Debug calendário:", { + totalDatasDisponiveis: availableDates.size, + primeiraData: dateStr, + diaDaSemana: day.getDay(), + isAvailable, + isUnavailable, + datas5Primeiras: Array.from(availableDates).slice(0, 5) + }); + } + return (
-
-

🟢 Datas disponíveis

-

🔴 Datas bloqueadas

+
+

Datas disponíveis

+

Datas indisponíveis

+

Datas passadas

-
+
-
{selectedDate && availableSlots.length > 0 ? ( -
+
{availableSlots.map((slot) => ( ))}
) : selectedDate ? ( -
-

+

+

Nenhum horário disponível para esta data

) : ( -
-

+

+

Selecione uma data para ver os horários

)}
-