2025-10-20 21:49:35 -03:00

229 lines
9.4 KiB
TypeScript

"use client";
import type React from "react";
import { useState, useEffect, useCallback } from "react";
import { agendamentosApi, Appointment } from "@/services/agendamentosApi";
import { Patient } from "@/services/pacientesApi";
import { Doctor } from "@/services/medicosApi";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Clock, Calendar as CalendarIcon, MapPin, Phone, User, X, RefreshCw, Loader2 } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { toast } from "sonner";
import { Calendar } from "@/components/ui/calendar";
import { format, parseISO, isSameDay } from "date-fns";
// Interface corrigida para incluir os tipos completos de Patient e Doctor
interface AppointmentWithDetails extends Appointment {
patients: Patient;
doctors: Doctor;
}
export default function DoctorAppointmentsPage() {
const [allAppointments, setAllAppointments] = useState<AppointmentWithDetails[]>([]);
const [filteredAppointments, setFilteredAppointments] = useState<AppointmentWithDetails[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [bookedDays, setBookedDays] = useState<Date[]>([]);
const [selectedCalendarDate, setSelectedCalendarDate] = useState<Date | undefined>(new Date());
const fetchAppointments = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
// A camada de serviço deve ser ajustada para buscar os dados aninhados
// Ex: api.get('/rest/v1/appointments?select=*,patients(*),doctors(*)')
const data = await agendamentosApi.list() as AppointmentWithDetails[];
setAllAppointments(data || []);
const uniqueBookedDates = Array.from(new Set(data.map(app => app.scheduled_at.split('T')[0])));
const dateObjects = uniqueBookedDates.map(dateString => parseISO(dateString));
setBookedDays(dateObjects);
toast.success("Agenda atualizada com sucesso!");
} catch (e) {
console.error("Erro ao carregar a agenda:", e);
setError("Não foi possível carregar sua agenda. Verifique a conexão.");
setAllAppointments([]);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
fetchAppointments();
}, [fetchAppointments]);
useEffect(() => {
if (selectedCalendarDate) {
const todayAppointments = allAppointments
.filter(app => isSameDay(parseISO(app.scheduled_at), selectedCalendarDate))
.sort((a, b) => a.scheduled_at.localeCompare(b.scheduled_at));
setFilteredAppointments(todayAppointments);
} else {
const todayAppointments = allAppointments
.filter(app => isSameDay(parseISO(app.scheduled_at), new Date()))
.sort((a, b) => a.scheduled_at.localeCompare(b.scheduled_at));
setFilteredAppointments(todayAppointments);
}
}, [allAppointments, selectedCalendarDate]);
const getStatusVariant = (status: Appointment['status']) => {
switch (status) {
case "confirmed":
case "requested":
return "default";
case "completed":
return "secondary";
case "cancelled":
return "destructive";
default:
return "outline";
}
};
const handleCancel = async (id: string) => {
try {
await agendamentosApi.update(id, { status: "cancelled" });
toast.info(`Consulta cancelada com sucesso.`);
await fetchAppointments();
} catch (error) {
console.error("Erro ao cancelar consulta:", error);
toast.error("Não foi possível cancelar a consulta.");
}
};
const handleReSchedule = (id: string) => {
toast.info(`Reagendamento da Consulta ID: ${id}. Navegar para a página de agendamento.`);
};
const displayDate = selectedCalendarDate
? format(selectedCalendarDate, "EEEE, dd 'de' MMMM")
: "Selecione uma data";
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-gray-900">Agenda Médica</h1>
<p className="text-gray-600">Visualize e gerencie todas as suas consultas.</p>
</div>
<div className="flex justify-between items-center">
<h2 className="text-xl font-semibold">Consultas para: {displayDate}</h2>
<Button onClick={fetchAppointments} disabled={isLoading} variant="outline" size="sm">
<RefreshCw className={`mr-2 h-4 w-4 ${isLoading ? 'animate-spin' : ''}`} />
Atualizar Agenda
</Button>
</div>
<div className="grid lg:grid-cols-3 gap-6">
<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
modifiers={{ booked: bookedDays }}
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>
<div className="lg:col-span-2 space-y-4">
{isLoading ? (
<div className="text-center text-lg text-gray-500 p-8">
<Loader2 className="w-8 h-8 animate-spin mx-auto mb-3 text-blue-600" />
Carregando a agenda...
</div>
) : error ? (
<div className="text-center text-lg text-red-600 p-8">{error}</div>
) : filteredAppointments.length === 0 ? (
<p className="text-center text-lg text-gray-500 p-8">Nenhuma consulta encontrada para a data selecionada.</p>
) : (
filteredAppointments.map((appointment) => {
const showActions = appointment.status === "requested" || appointment.status === "confirmed";
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.patients?.full_name || 'Paciente não informado'}
</CardTitle>
<Badge variant={getStatusVariant(appointment.status)} className="uppercase">
{appointment.status}
</Badge>
</CardHeader>
<CardContent className="grid md:grid-cols-3 gap-4 pt-4">
<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.doctors?.full_name || 'N/A'}
</div>
<div className="flex items-center text-sm text-gray-700">
<CalendarIcon className="mr-2 h-4 w-4 text-gray-500" />
{format(parseISO(appointment.scheduled_at), 'dd/MM/yyyy')}
</div>
<div className="flex items-center text-sm text-gray-700">
<Clock className="mr-2 h-4 w-4 text-gray-500" />
{format(parseISO(appointment.scheduled_at), 'HH:mm')}
</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.appointment_type || 'N/A'}
</div>
<div className="flex items-center text-sm text-gray-700">
<Phone className="mr-2 h-4 w-4 text-gray-500" />
{appointment.patients?.phone_mobile || "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>
);
}