2025-10-23 01:39:29 -03:00

368 lines
15 KiB
TypeScript

// Caminho: app/(patient)/schedule/page.tsx (Completo e Corrigido)
"use client";
import type React from "react";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { format, getDay } from "date-fns";
import { ptBR } from "date-fns/locale";
// A importação do PatientLayout foi REMOVIDA
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 { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Calendar as CalendarIcon, Clock, User as UserIcon } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { usuariosApi, User } from "@/services/usuariosApi";
import { medicosApi, Doctor } from "@/services/medicosApi";
import { agendamentosApi } from "@/services/agendamentosApi";
import { disponibilidadeApi, DoctorAvailability, DoctorException } from "@/services/disponibilidadeApi";
interface AvailabilityRules {
weekly: DoctorAvailability[];
exceptions: DoctorException[];
}
export default function ScheduleAppointment() {
const router = useRouter();
const [user, setUser] = useState<User | null>(null);
const [doctors, setDoctors] = useState<Doctor[]>([]);
const [availableSlots, setAvailableSlots] = useState<string[]>([]);
const [availabilityRules, setAvailabilityRules] = useState<AvailabilityRules | null>(null);
const [isAvailabilityLoading, setIsAvailabilityLoading] = useState(false);
const [formData, setFormData] = useState<{
doctorId: string;
date: Date | undefined;
time: string;
appointmentType: string;
duration: string;
reason: string;
}>({
doctorId: "",
date: undefined,
time: "",
appointmentType: "presencial",
duration: "30",
reason: "",
});
const [isLoading, setIsLoading] = useState(true);
const [isSlotsLoading, setIsSlotsLoading] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadInitialData = async () => {
try {
const currentUser = await usuariosApi.getCurrentUser();
setUser(currentUser);
let activeDoctors = await medicosApi.list({ active: true });
if (activeDoctors.length === 0) {
console.warn("Nenhum médico ativo encontrado. Buscando do mock...");
toast.info("Usando dados de exemplo para a lista de médicos.");
activeDoctors = await medicosApi.getMockDoctors();
}
setDoctors(activeDoctors);
} catch (e) {
console.error("Erro ao carregar dados iniciais:", e);
setError("Não foi possível carregar os dados necessários para o agendamento.");
} finally {
setIsLoading(false);
}
};
loadInitialData();
}, []);
useEffect(() => {
const fetchDoctorAvailability = async () => {
if (!formData.doctorId) {
setAvailabilityRules(null);
return;
}
if (formData.doctorId.startsWith("mock-")) {
setAvailabilityRules({ weekly: [], exceptions: [] });
return;
}
setIsAvailabilityLoading(true);
try {
const [weekly, exceptions] = await Promise.all([
disponibilidadeApi.list({ doctor_id: formData.doctorId, active: true }),
disponibilidadeApi.listExceptions({ doctor_id: formData.doctorId }),
]);
setAvailabilityRules({ weekly, exceptions });
} catch (err) {
console.error("Erro ao buscar disponibilidade do médico:", err);
toast.error("Não foi possível carregar a agenda do médico.");
setAvailabilityRules(null);
} finally {
setIsAvailabilityLoading(false);
}
};
fetchDoctorAvailability();
}, [formData.doctorId]);
const fetchAvailableSlots = (doctorId: string, date: Date | undefined) => {
if (!doctorId || !date) return;
setIsSlotsLoading(true);
setAvailableSlots([]);
if (doctorId.startsWith("mock-")) {
setTimeout(() => {
const mockSlots = ["09:00", "10:00", "11:00", "14:00", "15:00"];
setAvailableSlots(mockSlots);
setIsSlotsLoading(false);
}, 500);
return;
}
const formattedDate = format(date, "yyyy-MM-dd");
agendamentosApi.getAvailableSlots(doctorId, formattedDate)
.then(response => {
const slots = response.slots.filter(s => s.available).map(s => s.time);
setAvailableSlots(slots);
})
.catch(() => toast.error("Não foi possível buscar horários para esta data."))
.finally(() => setIsSlotsLoading(false));
};
const handleSelectChange = (name: keyof typeof formData) => (value: string | Date | undefined) => {
const newFormData = { ...formData, [name]: value } as any;
if (name === 'doctorId') {
newFormData.date = undefined;
newFormData.time = "";
}
if (name === 'date') {
newFormData.time = "";
fetchAvailableSlots(newFormData.doctorId, newFormData.date);
}
setFormData(newFormData);
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { id, value } = e.target;
setFormData(prev => ({ ...prev, [id]: value }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!user?.id || !formData.date) {
toast.error("Erro de autenticação ou data inválida.");
return;
}
if (formData.doctorId.startsWith("mock-")) {
toast.success("Simulação de agendamento com médico de exemplo concluída!");
router.push("/patient/appointments");
return;
}
setIsSubmitting(true);
try {
const newScheduledAt = new Date(`${format(formData.date, "yyyy-MM-dd")}T${formData.time}:00Z`).toISOString();
await agendamentosApi.create({
doctor_id: formData.doctorId,
patient_id: user.id,
scheduled_at: newScheduledAt,
duration_minutes: parseInt(formData.duration, 10),
appointment_type: formData.appointmentType as 'presencial' | 'telemedicina',
status: "requested",
created_by: user.id,
notes: formData.reason,
});
toast.success("Consulta agendada com sucesso!");
router.push("/patient/appointments");
} catch (error) {
console.error("Erro ao agendar consulta:", error);
toast.error("Falha ao agendar a consulta. Tente novamente.");
} finally {
setIsSubmitting(false);
}
};
const isDateDisabled = (date: Date): boolean => {
if (date < new Date(new Date().setDate(new Date().getDate() - 1))) {
return true;
}
if (!availabilityRules) {
return false;
}
const dateString = format(date, "yyyy-MM-dd");
const dayOfWeek = getDay(date);
const fullDayBlock = availabilityRules.exceptions.find(
ex => ex.date === dateString && ex.kind === 'bloqueio' && !ex.start_time
);
if (fullDayBlock) {
return true;
}
const worksOnThisDay = availabilityRules.weekly.some(
avail => avail.weekday === dayOfWeek
);
return !worksOnThisDay;
};
const selectedDoctorDetails = doctors.find((d) => d.id === formData.doctorId);
const isFormInvalid = !formData.doctorId || !formData.date || !formData.time || isSubmitting;
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Agendar Consulta</h1>
<p className="text-muted-foreground">Escolha o médico, data e horário para sua consulta</p>
</div>
{isLoading ? (
<p className="text-muted-foreground">Carregando...</p>
) : error ? (
<p className="text-destructive">{error}</p>
) : (
<div className="grid lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
<Card>
<CardHeader>
<CardTitle>Dados da Consulta</CardTitle>
<CardDescription>Preencha as informações para agendar sua consulta</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-2">
<Label htmlFor="doctorId">Médico</Label>
<Select value={formData.doctorId} onValueChange={handleSelectChange('doctorId')}>
<SelectTrigger><SelectValue placeholder="Selecione um médico" /></SelectTrigger>
<SelectContent>
{doctors.length > 0 ? (
doctors.map((doctor) => (
<SelectItem key={doctor.id} value={doctor.id}>
{doctor.full_name} - {doctor.specialty}
</SelectItem>
))
) : (
<div className="p-2 text-center text-sm text-muted-foreground">Nenhum médico disponível</div>
)}
</SelectContent>
</Select>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="date">Data</Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn("w-full justify-start text-left font-normal", !formData.date && "text-muted-foreground")}
disabled={!formData.doctorId || isAvailabilityLoading}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{isAvailabilityLoading ? "Carregando agenda..." : formData.date ? format(formData.date, "PPP", { locale: ptBR }) : <span>Escolha uma data</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={formData.date}
onSelect={handleSelectChange('date')}
initialFocus
disabled={isDateDisabled}
/>
</PopoverContent>
</Popover>
</div>
<div className="space-y-2">
<Label htmlFor="time">Horário</Label>
<Select value={formData.time} onValueChange={handleSelectChange('time')} disabled={!formData.date || isSlotsLoading}>
<SelectTrigger>
<SelectValue placeholder={isSlotsLoading ? "Carregando..." : "Selecione um horário"} />
</SelectTrigger>
<SelectContent>
{isSlotsLoading ? (
<div className="p-2 text-center text-sm text-muted-foreground">Carregando horários...</div>
) : availableSlots.length > 0 ? (
availableSlots.map((time) => <SelectItem key={time} value={time}>{time}</SelectItem>)
) : (
<div className="p-2 text-center text-sm text-muted-foreground">
{formData.date ? "Nenhum horário disponível" : "Selecione uma data"}
</div>
)}
</SelectContent>
</Select>
</div>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="appointmentType">Tipo de Consulta</Label>
<Select value={formData.appointmentType} onValueChange={handleSelectChange('appointmentType')}>
<SelectTrigger id="appointmentType"><SelectValue placeholder="Selecione o tipo" /></SelectTrigger>
<SelectContent>
<SelectItem value="presencial">Presencial</SelectItem>
<SelectItem value="telemedicina">Telemedicina</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="duration">Duração (minutos)</Label>
<Input id="duration" type="number" min={10} max={120} value={formData.duration} onChange={handleInputChange} />
</div>
</div>
<div className="space-y-2">
<Label htmlFor="reason">Queixa Principal / Observações (opcional)</Label>
<Textarea id="reason" placeholder="Descreva brevemente o motivo da consulta ou observações importantes..." value={formData.reason} onChange={handleInputChange} rows={3} />
</div>
<Button type="submit" className="w-full" disabled={isFormInvalid}>
{isSubmitting ? "Agendando..." : "Agendar Consulta"}
</Button>
</form>
</CardContent>
</Card>
</div>
<div className="space-y-6">
<Card>
<CardHeader><CardTitle className="flex items-center"><CalendarIcon className="mr-2 h-5 w-5" /> Resumo</CardTitle></CardHeader>
<CardContent className="space-y-4">
{selectedDoctorDetails && <div className="flex items-center space-x-2"><UserIcon className="h-4 w-4 text-muted-foreground" /><span className="text-sm">{selectedDoctorDetails.full_name}</span></div>}
{formData.date && <div className="flex items-center space-x-2"><CalendarIcon className="h-4 w-4 text-muted-foreground" /><span className="text-sm">{format(formData.date, "PPP", { locale: ptBR })}</span></div>}
{formData.time && <div className="flex items-center space-x-2"><Clock className="h-4 w-4 text-muted-foreground" /><span className="text-sm">{formData.time}</span></div>}
</CardContent>
</Card>
<Card>
<CardHeader><CardTitle>Informações Importantes</CardTitle></CardHeader>
<CardContent className="text-sm text-muted-foreground 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>
);
}