adicionando calendário para as consultas

This commit is contained in:
Lucas Rodrigues 2025-10-08 18:24:09 -03:00
parent 4af7c35f73
commit 452d4147dd
5 changed files with 305 additions and 305 deletions

View File

@ -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<LocalStorageAppointment[]>([]);
const [allAppointments, setAllAppointments] = useState<LocalStorageAppointment[]>([]);
const [filteredAppointments, setFilteredAppointments] = useState<LocalStorageAppointment[]>([]);
const [isLoading, setIsLoading] = useState(true);
// NOVO ESTADO 1: Armazena os dias com consultas (para o calendário)
const [bookedDays, setBookedDays] = useState<Date[]>([]);
// NOVO ESTADO 2: Armazena a data selecionada no calendário
const [selectedCalendarDate, setSelectedCalendarDate] = useState<Date | undefined>(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 (
<DoctorLayout>
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-gray-900">Minhas Consultas</h1>
<p className="text-gray-600">Agenda atual ({LOGGED_IN_DOCTOR_NAME}) e histórico de atendimentos</p>
<h1 className="text-3xl font-bold text-gray-900">Agenda Médica Centralizada</h1>
<p className="text-gray-600">Todas as consultas do sistema são exibidas aqui ({LOGGED_IN_DOCTOR_NAME})</p>
</div>
<div className="flex justify-end">
<div className="flex justify-between items-center">
<h2 className="text-xl font-semibold">Consultas para: {displayDate}</h2>
<Button onClick={loadAppointments} disabled={isLoading} variant="outline" size="sm">
<RefreshCw className={`mr-2 h-4 w-4 ${isLoading ? 'animate-spin' : ''}`} />
Atualizar Agenda
</Button>
</div>
<div className="space-y-4">
{isLoading ? (
<p className="text-center text-lg text-gray-500">Carregando a agenda...</p>
) : appointments.length === 0 ? (
<p className="text-center text-lg text-gray-500">Nenhuma consulta agendada para você (Médico: {LOGGED_IN_DOCTOR_NAME}).</p>
) : (
appointments.map((appointment) => {
// Formatação de data e hora
const showActions = appointment.status === "agendada" || appointment.status === "confirmada";
{/* NOVO LAYOUT DE DUAS COLUNAS */}
<div className="grid lg:grid-cols-3 gap-6">
{/* COLUNA 1: CALENDÁRIO */}
<div className="lg:col-span-1">
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<CalendarIcon className="mr-2 h-5 w-5" />
Calendário
</CardTitle>
<p className="text-sm text-gray-500">Dias em azul possuem agendamentos.</p>
</CardHeader>
<CardContent className="flex justify-center p-2">
<Calendar
mode="single"
selected={selectedCalendarDate}
onSelect={setSelectedCalendarDate}
initialFocus
// A CHAVE DO HIGHLIGHT: Passa o array de datas agendadas
modifiers={{ booked: bookedDays }}
// Define o estilo CSS para o modificador 'booked'
modifiersClassNames={{
booked: "bg-blue-600 text-white aria-selected:!bg-blue-700 hover:!bg-blue-700/90"
}}
className="rounded-md border p-2"
/>
</CardContent>
</Card>
</div>
return (
<Card key={appointment.id} className="shadow-lg">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
{/* NOME DO PACIENTE */}
<CardTitle className="text-xl font-semibold flex items-center">
<User className="mr-2 h-5 w-5 text-blue-600" />
{appointment.patientName}
</CardTitle>
{/* STATUS DA CONSULTA */}
<Badge variant={getStatusVariant(appointment.status)} className="uppercase">
{appointment.status}
</Badge>
</CardHeader>
{/* COLUNA 2: LISTA DE CONSULTAS FILTRADAS */}
<div className="lg:col-span-2 space-y-4">
{isLoading ? (
<p className="text-center text-lg text-gray-500">Carregando a agenda...</p>
) : filteredAppointments.length === 0 ? (
<p className="text-center text-lg text-gray-500">Nenhuma consulta encontrada para a data selecionada.</p>
) : (
filteredAppointments.map((appointment) => {
const showActions = appointment.status === "agendada" || appointment.status === "confirmada";
<CardContent className="grid md:grid-cols-3 gap-4 pt-4">
{/* COLUNA 1: Data e Hora */}
<div className="space-y-3">
<div className="flex items-center text-sm text-gray-700">
<Calendar className="mr-2 h-4 w-4 text-gray-500" />
{new Date(appointment.date).toLocaleDateString("pt-BR", { timeZone: "UTC" })}
</div>
<div className="flex items-center text-sm text-gray-700">
<Clock className="mr-2 h-4 w-4 text-gray-500" />
{appointment.time}
</div>
</div>
return (
<Card key={appointment.id} className="shadow-lg">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-xl font-semibold flex items-center">
<User className="mr-2 h-5 w-5 text-blue-600" />
{appointment.patientName}
</CardTitle>
<Badge variant={getStatusVariant(appointment.status)} className="uppercase">
{appointment.status}
</Badge>
</CardHeader>
{/* COLUNA 2: Local e Contato */}
<div className="space-y-3">
<div className="flex items-center text-sm text-gray-700">
<MapPin className="mr-2 h-4 w-4 text-gray-500" />
{appointment.location}
</div>
<div className="flex items-center text-sm text-gray-700">
<Phone className="mr-2 h-4 w-4 text-gray-500" />
{/* 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")}
</div>
</div>
{/* COLUNA 3: Ações (Botões) */}
<div className="flex flex-col justify-center items-end">
{showActions && (
<div className="flex space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => handleReSchedule(appointment.id)}
>
<RefreshCw className="mr-2 h-4 w-4" />
Reagendar
</Button>
<Button
variant="destructive"
size="sm"
onClick={() => handleCancel(appointment.id)}
>
<X className="mr-2 h-4 w-4" />
Cancelar
</Button>
<CardContent className="grid md:grid-cols-3 gap-4 pt-4">
{/* Detalhes e Ações... (mantidos) */}
<div className="space-y-3">
<div className="flex items-center text-sm text-gray-700">
<User className="mr-2 h-4 w-4 text-gray-500" />
<span className="font-semibold">Médico:</span> {appointment.doctor}
</div>
)}
</div>
</CardContent>
</Card>
);
})
)}
<div className="flex items-center text-sm text-gray-700">
<CalendarIcon className="mr-2 h-4 w-4 text-gray-500" />
{new Date(appointment.date).toLocaleDateString("pt-BR", { timeZone: "UTC" })}
</div>
<div className="flex items-center text-sm text-gray-700">
<Clock className="mr-2 h-4 w-4 text-gray-500" />
{appointment.time}
</div>
</div>
<div className="space-y-3">
<div className="flex items-center text-sm text-gray-700">
<MapPin className="mr-2 h-4 w-4 text-gray-500" />
{appointment.location}
</div>
<div className="flex items-center text-sm text-gray-700">
<Phone className="mr-2 h-4 w-4 text-gray-500" />
{appointment.phone || "N/A"}
</div>
</div>
<div className="flex flex-col justify-center items-end">
{showActions && (
<div className="flex space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => handleReSchedule(appointment.id)}
>
<RefreshCw className="mr-2 h-4 w-4" />
Reagendar
</Button>
<Button
variant="destructive"
size="sm"
onClick={() => handleCancel(appointment.id)}
>
<X className="mr-2 h-4 w-4" />
Cancelar
</Button>
</div>
)}
</div>
</CardContent>
</Card>
);
})
)}
</div>
</div>
</div>
</DoctorLayout>

View File

@ -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%;
}

View File

@ -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<Doctor[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// [SINCRONIZAÇÃO 1 - continuação] - Obtendo a lista de agendamentos existentes
const { addAppointment, appointments } = useAppointments();
const [formData, setFormData] = useState<AppointmentFormState>({
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<HTMLInputElement | HTMLTextAreaElement>) => {
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 (
<PatientLayout>
@ -139,7 +100,7 @@ export default function ScheduleAppointmentPage() {
<p className="text-gray-600">Escolha o médico, data e horário para sua consulta</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="grid lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
<Card>
<CardHeader>
@ -150,41 +111,35 @@ export default function ScheduleAppointmentPage() {
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-2">
<Label htmlFor="doctor">Médico</Label>
<Select
value={formData.id}
onValueChange={(value) => handleSelectChange('id', value)}
>
<Select value={selectedDoctor} onValueChange={setSelectedDoctor}>
<SelectTrigger>
<SelectValue placeholder="Seleione um médico" />
<SelectValue placeholder="Selecione um médico" />
</SelectTrigger>
<SelectContent>
{doctors.map((doctor) => (
<SelectItem key={doctor.id} value={doctor.id}>
{doctor.full_name} - {doctor.specialty}
{doctor.name} - {doctor.specialty}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="date">Data</Label>
<Input
id="date"
name="date"
type="date"
value={formData.date}
onChange={handleChange}
min={today}
value={selectedDate}
onChange={(e) => setSelectedDate(e.target.value)}
min={new Date().toISOString().split("T")[0]}
/>
</div>
<div className="space-y-2">
<Label htmlFor="time">Horário</Label>
<Select
value={formData.time}
onValueChange={(value) => handleSelectChange('time', value)}
>
<Select value={selectedTime} onValueChange={setSelectedTime}>
<SelectTrigger>
<SelectValue placeholder="Selecione um horário" />
</SelectTrigger>
@ -200,18 +155,17 @@ export default function ScheduleAppointmentPage() {
</div>
<div className="space-y-2">
<Label htmlFor="observations">Observações (opcional)</Label>
<Label htmlFor="notes">Observações (opcional)</Label>
<Textarea
id="observations"
name="observations"
id="notes"
placeholder="Descreva brevemente o motivo da consulta ou observações importantes"
value={formData.observations}
onChange={handleChange}
rows={4}
value={notes}
onChange={(e) => setNotes(e.target.value)}
rows={3}
/>
</div>
<Button type="submit" className="w-full bg-gray-600 hover:bg-gray-700 text-white text-base py-6">
<Button type="submit" className="w-full" disabled={!selectedDoctor || !selectedDate || !selectedTime}>
Agendar Consulta
</Button>
</form>
@ -222,30 +176,30 @@ export default function ScheduleAppointmentPage() {
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center text-base">
<CardTitle className="flex items-center">
<Calendar className="mr-2 h-5 w-5" />
Resumo
</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm">
{formData.id ? (
<div className="flex items-center">
<User className="mr-2 h-4 w-4 text-gray-500" />
<span>{doctors.find((d) => d.id === formData.id)?.full_name}</span>
</div>
) : <p className="text-gray-500">Preencha o formulário...</p>}
{formData.date && (
<div className="flex items-center">
<Calendar className="mr-2 h-4 w-4 text-gray-500" />
<span>{new Date(formData.date).toLocaleDateString("pt-BR", { timeZone: 'UTC' })}</span>
<CardContent className="space-y-4">
{selectedDoctor && (
<div className="flex items-center space-x-2">
<User className="h-4 w-4 text-gray-500" />
<span className="text-sm">{doctors.find((d) => d.id === selectedDoctor)?.name}</span>
</div>
)}
{formData.time && (
<div className="flex items-center">
<Clock className="mr-2 h-4 w-4 text-gray-500" />
<span>{formData.time}</span>
{selectedDate && (
<div className="flex items-center space-x-2">
<Calendar className="h-4 w-4 text-gray-500" />
<span className="text-sm">{new Date(selectedDate).toLocaleDateString("pt-BR")}</span>
</div>
)}
{selectedTime && (
<div className="flex items-center space-x-2">
<Clock className="h-4 w-4 text-gray-500" />
<span className="text-sm">{selectedTime}</span>
</div>
)}
</CardContent>
@ -253,20 +207,18 @@ export default function ScheduleAppointmentPage() {
<Card>
<CardHeader>
<CardTitle className="text-base">Informações Importantes</CardTitle>
<CardTitle>Informações Importantes</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-2 text-sm text-gray-600 list-disc list-inside">
<li>Chegue com 15 minutos de antecedência</li>
<li>Traga documento com foto</li>
<li>Traga carteirinha do convênio</li>
<li>Traga exames anteriores, se houver</li>
</ul>
<CardContent className="text-sm text-gray-600 space-y-2">
<p> Chegue com 15 minutos de antecedência</p>
<p> Traga documento com foto</p>
<p> Traga carteirinha do convênio</p>
<p> Traga exames anteriores, se houver</p>
</CardContent>
</Card>
</div>
</div>
</div>
</PatientLayout>
);
)
}

14
package-lock.json generated
View File

@ -46,7 +46,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
"date-fns": "4.1.0",
"date-fns": "^4.1.0",
"embla-carousel-react": "8.5.1",
"geist": "^1.3.1",
"input-otp": "1.4.1",
@ -54,13 +54,13 @@
"next": "14.2.16",
"next-themes": "^0.4.6",
"react": "^18",
"react-day-picker": "9.8.0",
"react-day-picker": "^9.8.0",
"react-dom": "^18",
"react-hook-form": "^7.60.0",
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.4",
"sonner": "latest",
"tailwind-merge": "^2.5.5",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.9",
"zod": "3.25.67"
@ -2499,14 +2499,14 @@
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.24",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz",
"integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@ -2517,7 +2517,7 @@
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true,
"devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
@ -3579,7 +3579,6 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -4126,7 +4125,6 @@
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
"integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
"dev": true,
"license": "MIT"
},
"node_modules/tailwindcss-animate": {

View File

@ -47,7 +47,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
"date-fns": "4.1.0",
"date-fns": "^4.1.0",
"embla-carousel-react": "8.5.1",
"geist": "^1.3.1",
"input-otp": "1.4.1",
@ -55,13 +55,13 @@
"next": "14.2.16",
"next-themes": "^0.4.6",
"react": "^18",
"react-day-picker": "9.8.0",
"react-day-picker": "^9.8.0",
"react-dom": "^18",
"react-hook-form": "^7.60.0",
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.4",
"sonner": "latest",
"tailwind-merge": "^2.5.5",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.9",
"zod": "3.25.67"