From 452d4147ddddc816af78de473da069b96ce2e71d Mon Sep 17 00:00:00 2001 From: Lucas Rodrigues Date: Wed, 8 Oct 2025 18:24:09 -0300 Subject: [PATCH] =?UTF-8?q?adicionando=20calend=C3=A1rio=20para=20as=20con?= =?UTF-8?q?sultas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/doctor/medicos/consultas/page.tsx | 282 +++++++++++++++---------- app/globals.css | 20 -- app/patient/schedule/page.tsx | 288 +++++++++++--------------- package-lock.json | 14 +- package.json | 6 +- 5 files changed, 305 insertions(+), 305 deletions(-) diff --git a/app/doctor/medicos/consultas/page.tsx b/app/doctor/medicos/consultas/page.tsx index ed91ae3..57c658c 100644 --- a/app/doctor/medicos/consultas/page.tsx +++ b/app/doctor/medicos/consultas/page.tsx @@ -5,60 +5,94 @@ import { useState, useEffect } from "react"; import DoctorLayout from "@/components/doctor-layout"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { Clock, Calendar, MapPin, Phone, User, X, RefreshCw } from "lucide-react"; +import { Clock, Calendar as CalendarIcon, MapPin, Phone, User, X, RefreshCw } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { toast } from "sonner"; +// IMPORTAR O COMPONENTE CALENDÁRIO DA SHADCN +import { Calendar } from "@/components/ui/calendar"; +import { format } from "date-fns"; // Usaremos o date-fns para formatação e comparação de datas + const APPOINTMENTS_STORAGE_KEY = "clinic-appointments"; // --- TIPAGEM DA CONSULTA SALVA NO LOCALSTORAGE --- -// Reflete a estrutura salva pelo secretarypage.tsx interface LocalStorageAppointment { - id: number; // ID único simples (timestamp) + id: number; patientName: string; - doctor: string; // Nome completo do médico (para filtrar) + doctor: string; specialty: string; - date: string; // Data no formato YYYY-MM-DD - time: string; // Hora no formato HH:MM + date: string; // Data no formato YYYY-MM-DD + time: string; // Hora no formato HH:MM status: "agendada" | "confirmada" | "cancelada" | "realizada"; location: string; phone: string; } -// --- SIMULAÇÃO DO MÉDICO LOGADO --- -// **IMPORTANTE**: Em um ambiente real, este valor viria do seu sistema de autenticação. -// Use um nome que corresponda a um médico que você cadastrou e usou para agendar. -const LOGGED_IN_DOCTOR_NAME = "Dr. João Silva"; // <--- AJUSTE ESTE NOME PARA TESTAR +const LOGGED_IN_DOCTOR_NAME = "Dr. João Santos"; + +// Função auxiliar para comparar se duas datas (Date objects) são o mesmo dia +const isSameDay = (date1: Date, date2: Date) => { + return date1.getFullYear() === date2.getFullYear() && + date1.getMonth() === date2.getMonth() && + date1.getDate() === date2.getDate(); +}; // --- COMPONENTE PRINCIPAL --- export default function DoctorAppointmentsPage() { - const [appointments, setAppointments] = useState([]); + const [allAppointments, setAllAppointments] = useState([]); + const [filteredAppointments, setFilteredAppointments] = useState([]); const [isLoading, setIsLoading] = useState(true); + + // NOVO ESTADO 1: Armazena os dias com consultas (para o calendário) + const [bookedDays, setBookedDays] = useState([]); + + // NOVO ESTADO 2: Armazena a data selecionada no calendário + const [selectedCalendarDate, setSelectedCalendarDate] = useState(new Date()); useEffect(() => { loadAppointments(); }, []); + // Efeito para filtrar a lista sempre que o calendário ou a lista completa for atualizada + useEffect(() => { + if (selectedCalendarDate) { + const dateString = format(selectedCalendarDate, 'yyyy-MM-dd'); + + // Filtra a lista completa de agendamentos pela data selecionada + const todayAppointments = allAppointments + .filter(app => app.date === dateString) + .sort((a, b) => a.time.localeCompare(b.time)); // Ordena por hora + + setFilteredAppointments(todayAppointments); + } else { + // Se nenhuma data estiver selecionada (ou se for limpa), mostra todos (ou os de hoje) + const todayDateString = format(new Date(), 'yyyy-MM-dd'); + const todayAppointments = allAppointments + .filter(app => app.date === todayDateString) + .sort((a, b) => a.time.localeCompare(b.time)); + + setFilteredAppointments(todayAppointments); + } + }, [allAppointments, selectedCalendarDate]); + const loadAppointments = () => { setIsLoading(true); try { const storedAppointmentsRaw = localStorage.getItem(APPOINTMENTS_STORAGE_KEY); - const allAppointments: LocalStorageAppointment[] = storedAppointmentsRaw ? JSON.parse(storedAppointmentsRaw) : []; + const allAppts: LocalStorageAppointment[] = storedAppointmentsRaw ? JSON.parse(storedAppointmentsRaw) : []; - // 1. FILTRAGEM CRÍTICA: Apenas as consultas para o médico logado - const filteredAppointments = allAppointments.filter( - (app) => app.doctor === LOGGED_IN_DOCTOR_NAME - ); + // ***** NENHUM FILTRO POR MÉDICO AQUI (Como solicitado) ***** + const appointmentsToShow = allAppts; - // 2. Ordena por Data e Hora - filteredAppointments.sort((a, b) => { - const dateTimeA = new Date(`${a.date}T${a.time}:00`); - const dateTimeB = new Date(`${b.date}T${b.time}:00`); - return dateTimeA.getTime() - dateTimeB.getTime(); - }); + // 1. EXTRAI E PREPARA AS DATAS PARA O CALENDÁRIO + const uniqueBookedDates = Array.from(new Set(appointmentsToShow.map(app => app.date))); + + // Converte YYYY-MM-DD para objetos Date, garantindo que o tempo seja meia-noite (00:00:00) + const dateObjects = uniqueBookedDates.map(dateString => new Date(dateString + 'T00:00:00')); - setAppointments(filteredAppointments); + setAllAppointments(appointmentsToShow); + setBookedDays(dateObjects); toast.success("Agenda atualizada com sucesso!"); } catch (error) { console.error("Erro ao carregar a agenda do LocalStorage:", error); @@ -68,8 +102,8 @@ export default function DoctorAppointmentsPage() { } }; - // Função utilitária para mapear o status para a cor da Badge const getStatusVariant = (status: LocalStorageAppointment['status']) => { + // ... (código mantido) switch (status) { case "confirmada": case "agendada": @@ -84,117 +118,153 @@ export default function DoctorAppointmentsPage() { }; const handleCancel = (id: number) => { - // Lógica para CANCELAR a consulta no LocalStorage + // ... (código mantido para cancelamento) const storedAppointmentsRaw = localStorage.getItem(APPOINTMENTS_STORAGE_KEY); - const allAppointments: LocalStorageAppointment[] = storedAppointmentsRaw ? JSON.parse(storedAppointmentsRaw) : []; + const allAppts: LocalStorageAppointment[] = storedAppointmentsRaw ? JSON.parse(storedAppointmentsRaw) : []; - const updatedAppointments = allAppointments.map(app => + const updatedAppointments = allAppts.map(app => app.id === id ? { ...app, status: "cancelada" as const } : app ); localStorage.setItem(APPOINTMENTS_STORAGE_KEY, JSON.stringify(updatedAppointments)); - loadAppointments(); // Recarrega a lista filtrada + loadAppointments(); toast.info(`Consulta cancelada com sucesso.`); }; const handleReSchedule = (id: number) => { - // Aqui você navegaria para a tela de agendamento passando o ID para pré-preencher toast.info(`Reagendamento da Consulta ID: ${id}. Navegar para a página de agendamento.`); }; + const displayDate = selectedCalendarDate ? + new Date(selectedCalendarDate).toLocaleDateString("pt-BR", {weekday: 'long', day: '2-digit', month: 'long'}) : + "Selecione uma data"; + + return (
-

Minhas Consultas

-

Agenda atual ({LOGGED_IN_DOCTOR_NAME}) e histórico de atendimentos

+

Agenda Médica Centralizada

+

Todas as consultas do sistema são exibidas aqui ({LOGGED_IN_DOCTOR_NAME})

-
+
+

Consultas para: {displayDate}

-
- {isLoading ? ( -

Carregando a agenda...

- ) : appointments.length === 0 ? ( -

Nenhuma consulta agendada para você (Médico: {LOGGED_IN_DOCTOR_NAME}).

- ) : ( - appointments.map((appointment) => { - // Formatação de data e hora - const showActions = appointment.status === "agendada" || appointment.status === "confirmada"; + {/* NOVO LAYOUT DE DUAS COLUNAS */} +
+ + {/* COLUNA 1: CALENDÁRIO */} +
+ + + + + Calendário + +

Dias em azul possuem agendamentos.

+
+ + + +
+
- return ( - - - {/* NOME DO PACIENTE */} - - - {appointment.patientName} - - {/* STATUS DA CONSULTA */} - - {appointment.status} - - + {/* COLUNA 2: LISTA DE CONSULTAS FILTRADAS */} +
+ {isLoading ? ( +

Carregando a agenda...

+ ) : filteredAppointments.length === 0 ? ( +

Nenhuma consulta encontrada para a data selecionada.

+ ) : ( + filteredAppointments.map((appointment) => { + const showActions = appointment.status === "agendada" || appointment.status === "confirmada"; - - {/* COLUNA 1: Data e Hora */} -
-
- - {new Date(appointment.date).toLocaleDateString("pt-BR", { timeZone: "UTC" })} -
-
- - {appointment.time} -
-
+ return ( + + + + + {appointment.patientName} + + + {appointment.status} + + - {/* COLUNA 2: Local e Contato */} -
-
- - {appointment.location} -
-
- - {/* Note: O telefone do paciente não está salvo no LocalStorage no seu código atual, usando um valor fixo */} - {(appointment.phone || "(11) 9XXXX-YYYY")} -
-
- - {/* COLUNA 3: Ações (Botões) */} -
- {showActions && ( -
- - + + {/* Detalhes e Ações... (mantidos) */} +
+
+ + Médico: {appointment.doctor}
- )} -
-
- - ); - }) - )} +
+ + {new Date(appointment.date).toLocaleDateString("pt-BR", { timeZone: "UTC" })} +
+
+ + {appointment.time} +
+
+ +
+
+ + {appointment.location} +
+
+ + {appointment.phone || "N/A"} +
+
+ +
+ {showActions && ( +
+ + +
+ )} +
+ + + ); + }) + )} +
diff --git a/app/globals.css b/app/globals.css index c68eae6..8384a1f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -122,24 +122,4 @@ body { @apply bg-background text-foreground; } -} - -.color-picker { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - width: 24px; - height: 24px; - border-radius: 50%; - border: 1px solid #ccc; - cursor: pointer; -} - -.color-picker::-webkit-color-swatch-wrapper { - padding: 0; -} - -.color-picker::-webkit-color-swatch { - border: none; - border-radius: 50%; } \ No newline at end of file diff --git a/app/patient/schedule/page.tsx b/app/patient/schedule/page.tsx index daa2dee..859bdc6 100644 --- a/app/patient/schedule/page.tsx +++ b/app/patient/schedule/page.tsx @@ -1,135 +1,96 @@ -"use client"; +"use client" -import type React from "react"; -import { useState, useEffect, useCallback } from "react"; -import { useRouter } from "next/navigation"; -import { toast } from "sonner"; +import type React from "react" +import { useState } from "react" +// Importações de componentes omitidas para brevidade, mas estão no código original +import PatientLayout from "@/components/patient-layout" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Textarea } from "@/components/ui/textarea" +import { Calendar, Clock, User } from "lucide-react" -// [SINCRONIZAÇÃO 1] - Importando a lista de 'appointments' para a validação de conflito -import { useAppointments } from "../../context/AppointmentsContext"; +// Chave do LocalStorage, a mesma usada em secretarypage.tsx +const APPOINTMENTS_STORAGE_KEY = "clinic-appointments"; -// Componentes de UI e Layout -import PatientLayout from "@/components/patient-layout"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Textarea } from "@/components/ui/textarea"; -import { Calendar, Clock, User } from "lucide-react"; -import { doctorsService } from "services/doctorsApi.mjs"; +export default function ScheduleAppointment() { + const [selectedDoctor, setSelectedDoctor] = useState("") + const [selectedDate, setSelectedDate] = useState("") + const [selectedTime, setSelectedTime] = useState("") + const [notes, setNotes] = useState("") -// Interface para o estado local do formulário (sem alterações) -interface AppointmentFormState { - id: string; - date: string; - time: string; - observations: string; -} + const doctors = [ + { id: "1", name: "Dr. João Silva", specialty: "Cardiologia" }, + { id: "2", name: "Dra. Maria Santos", specialty: "Dermatologia" }, + { id: "3", name: "Dr. Pedro Costa", specialty: "Ortopedia" }, + { id: "4", name: "Dra. Ana Lima", specialty: "Ginecologia" }, + ] -interface Doctor { - id: string; - full_name: string; - specialty: string; - phone_mobile: string; - -} - -// --- DADOS MOCKADOS (ALTERAÇÃO 1: Adicionando location e phone) --- -const doctors = [ - { id: "1", name: "Dr. João Silva", specialty: "Cardiologia", location: "Consultório A - 2º andar", phone: "(11) 3333-4444" }, - { id: "2", name: "Dra. Maria Santos", specialty: "Dermatologia", location: "Consultório B - 1º andar", phone: "(11) 3333-5555" }, - { id: "3", name: "Dr. Pedro Costa", specialty: "Ortopedia", location: "Consultório C - 3º andar", phone: "(11) 3333-6666" }, -]; -const availableTimes = ["09:00", "09:30", "10:00", "10:30", "14:00", "14:30", "15:00"]; -// ------------------------------------------------------------- - -export default function ScheduleAppointmentPage() { - const router = useRouter(); - const [doctors, setDoctors] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - // [SINCRONIZAÇÃO 1 - continuação] - Obtendo a lista de agendamentos existentes - const { addAppointment, appointments } = useAppointments(); - - const [formData, setFormData] = useState({ - id: "", - date: "", - time: "", - observations: "", - }); - - const fetchDoctors = useCallback(async () => { - setLoading(true); - setError(null); - try { - - const data: Doctor[] = await doctorsService.list(); - setDoctors(data || []); - } catch (e: any) { - console.error("Erro ao carregar lista de médicos:", e); - setError("Não foi possível carregar a lista de médicos. Verifique a conexão com a API."); - setDoctors([]); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - fetchDoctors(); - }, [fetchDoctors]); - - const handleChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData(prevState => ({ ...prevState, [name]: value })); - }; - - const handleSelectChange = (name: keyof AppointmentFormState, value: string) => { - setFormData(prevState => ({ ...prevState, [name]: value })); - }; + const availableTimes = [ + "08:00", + "08:30", + "09:00", + "09:30", + "10:00", + "10:30", + "14:00", + "14:30", + "15:00", + "15:30", + "16:00", + "16:30", + ] const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (!formData.id || !formData.date || !formData.time) { - toast.error("Por favor, preencha os campos de médico, data e horário."); + e.preventDefault() + + const doctorDetails = doctors.find((d) => d.id === selectedDoctor) + + // --- SIMULAÇÃO DO PACIENTE LOGADO --- + // Você só tem um usuário para cada role. Vamos simular um paciente: + const patientDetails = { + id: "P001", + full_name: "Paciente Exemplo Único", // Este nome aparecerá na agenda do médico + location: "Clínica Geral", + phone: "(11) 98765-4321" + }; + + if (!patientDetails || !doctorDetails) { + alert("Erro: Selecione o médico ou dados do paciente indisponíveis."); return; } - const selectedDoctor = doctors.find(doc => doc.id === formData.id); - if (!selectedDoctor) return; + const newAppointment = { + id: new Date().getTime(), // ID único simples + patientName: patientDetails.full_name, + doctor: doctorDetails.name, // Nome completo do médico (necessário para a listagem) + specialty: doctorDetails.specialty, + date: selectedDate, + time: selectedTime, + status: "agendada", + phone: patientDetails.phone, + }; - // Validação de conflito (sem alterações, já estava correta) - const isConflict = appointments.some( - (apt) => - apt.doctorName === selectedDoctor.full_name && - apt.date === formData.date && - apt.time === formData.time - ); + // 1. Carrega agendamentos existentes + const storedAppointmentsRaw = localStorage.getItem(APPOINTMENTS_STORAGE_KEY); + const currentAppointments = storedAppointmentsRaw ? JSON.parse(storedAppointmentsRaw) : []; + + // 2. Adiciona o novo agendamento + const updatedAppointments = [...currentAppointments, newAppointment]; - if (isConflict) { - toast.error("Este horário já está ocupado para o médico selecionado."); - return; - } + // 3. Salva a lista atualizada no LocalStorage + localStorage.setItem(APPOINTMENTS_STORAGE_KEY, JSON.stringify(updatedAppointments)); - // [ALTERAÇÃO 2] - Utilizando os dados do médico selecionado para location e phone - // e removendo os placeholders. - addAppointment({ - doctorName: selectedDoctor.full_name, - specialty: selectedDoctor.specialty, - date: formData.date, - time: formData.time, - observations: formData.observations, - phone: selectedDoctor.phone_mobile, - location: "" - }); - - toast.success("Consulta agendada com sucesso!"); - router.push('/patient/appointments'); - }; - - // Validação de data passada (sem alterações, já estava correta) - const today = new Date().toISOString().split('T')[0]; + alert(`Consulta com ${doctorDetails.name} agendada com sucesso!`); + + // Limpar o formulário após o sucesso (opcional) + setSelectedDoctor(""); + setSelectedDate(""); + setSelectedTime(""); + setNotes(""); + } return ( @@ -139,7 +100,7 @@ export default function ScheduleAppointmentPage() {

Escolha o médico, data e horário para sua consulta

-
+
@@ -150,41 +111,35 @@ export default function ScheduleAppointmentPage() {
- - + {doctors.map((doctor) => ( - {doctor.full_name} - {doctor.specialty} + {doctor.name} - {doctor.specialty} ))}
-
+
setSelectedDate(e.target.value)} + min={new Date().toISOString().split("T")[0]} />
+
- @@ -200,18 +155,17 @@ export default function ScheduleAppointmentPage() {
- +