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

248 lines
9.5 KiB
TypeScript

// Caminho: app/patient/dashboard/page.tsx
"use client";
import type React from "react";
import { useState, useEffect } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Calendar, Clock, User, Plus, LucideIcon } from "lucide-react";
import Link from "next/link";
import { toast } from "sonner";
// Importando TODOS os serviços de API necessários
import { usuariosApi } from "@/services/usuariosApi";
import { agendamentosApi, Appointment as ApiAppointment } from "@/services/agendamentosApi";
import { pacientesApi, Patient } from "@/services/pacientesApi";
// --- Componentes Reutilizáveis ---
interface DashboardStatCardProps {
title: string;
value: string;
description: string;
icon: LucideIcon;
}
const DashboardStatCard: React.FC<DashboardStatCardProps> = ({ title, value, description, icon: Icon }) => (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{title}</CardTitle>
<Icon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{value}</div>
<p className="text-xs text-muted-foreground">{description}</p>
</CardContent>
</Card>
);
interface AppointmentDisplay {
doctorName: string;
specialty: string;
date: string;
time: string;
}
interface UpcomingAppointmentItemProps {
appointment: AppointmentDisplay;
}
const UpcomingAppointmentItem: React.FC<UpcomingAppointmentItemProps> = ({ appointment }) => (
<div className="flex items-center justify-between p-3 bg-accent/50 rounded-lg">
<div>
<p className="font-medium">{appointment.doctorName}</p>
<p className="text-sm text-muted-foreground">{appointment.specialty}</p>
</div>
<div className="text-right">
<p className="font-medium">{appointment.date}</p>
<p className="text-sm text-muted-foreground">{appointment.time}</p>
</div>
</div>
);
// --- Tipos e Dados Estáticos ---
interface QuickAction {
href: string;
label: string;
icon: LucideIcon;
variant?: "outline";
}
const quickActions: QuickAction[] = [
{ href: "/patient/schedule", label: "Agendar Nova Consulta", icon: Plus, variant: "outline" },
{ href: "/patient/appointments", label: "Ver Minhas Consultas", icon: Calendar, variant: "outline" },
{ href: "/patient/profile", label: "Atualizar Dados", icon: User, variant: "outline" },
];
// --- Componente da Página ---
export default function PatientDashboard() {
const [statsData, setStatsData] = useState<DashboardStatCardProps[]>([]);
const [upcomingAppointments, setUpcomingAppointments] = useState<ApiAppointment[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
const user = await usuariosApi.getCurrentUser();
if (!user || !user.id) {
throw new Error("Usuário não autenticado.");
}
const [appointmentsResponse, patientResponse] = await Promise.allSettled([
agendamentosApi.listByPatient(user.id),
pacientesApi.getById(user.id)
]);
let appointments: ApiAppointment[] = [];
if (appointmentsResponse.status === 'fulfilled') {
appointments = appointmentsResponse.value;
// LÓGICA DE FALLBACK PARA AGENDAMENTOS
if (appointments.length === 0) {
console.warn("Nenhum agendamento encontrado na API real. Buscando do mock...");
toast.info("Usando dados de exemplo para os agendamentos.");
appointments = await agendamentosApi.getMockAppointments();
}
} else {
console.error("Falha ao buscar agendamentos:", appointmentsResponse.reason);
setError("Não foi possível carregar seus agendamentos. Tentando usar dados de exemplo.");
appointments = await agendamentosApi.getMockAppointments(); // Fallback em caso de erro
}
const upcoming = appointments
.filter(appt => new Date(appt.scheduled_at) > new Date() && appt.status !== 'cancelled')
.sort((a, b) => new Date(a.scheduled_at).getTime() - new Date(b.scheduled_at).getTime());
setUpcomingAppointments(upcoming);
let patientData: Patient | null = null;
if (patientResponse.status === 'fulfilled') {
patientData = patientResponse.value;
} else {
console.warn("Paciente não encontrado na API real. Tentando buscar do mock...", patientResponse.reason);
try {
patientData = await pacientesApi.getMockPatient();
toast.info("Usando dados de exemplo para o perfil do paciente.");
} catch (mockError) {
console.error("Falha ao buscar dados do mock:", mockError);
}
}
const nextAppointment = upcoming[0];
const appointmentsThisMonth = appointments.filter(appt => {
const apptDate = new Date(appt.scheduled_at);
const now = new Date();
return apptDate.getMonth() === now.getMonth() && apptDate.getFullYear() === now.getFullYear();
});
let profileCompleteness = 0;
let profileDescription = "Dados não encontrados";
if (patientData) {
const profileFields = ['nome_completo', 'cpf', 'email', 'telefone', 'data_nascimento', 'endereco', 'cidade', 'estado', 'cep', 'convenio'];
const filledFields = profileFields.filter(field => patientData[field]).length;
profileCompleteness = Math.round((filledFields / profileFields.length) * 100);
profileDescription = profileCompleteness === 100 ? "Dados completos" : `${filledFields} de ${profileFields.length} campos preenchidos`;
}
setStatsData([
{
title: "Próxima Consulta",
value: nextAppointment ? new Date(nextAppointment.scheduled_at).toLocaleDateString('pt-BR', { day: '2-digit', month: 'short' }) : "Nenhuma",
description: nextAppointment ? `${nextAppointment.doctors?.full_name || 'Médico'} - ${new Date(nextAppointment.scheduled_at).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}` : "Sem consultas futuras",
icon: Calendar
},
{
title: "Consultas Este Mês",
value: appointmentsThisMonth.length.toString(),
description: `${appointmentsThisMonth.filter(a => a.status === 'completed').length} realizadas`,
icon: Clock
},
{
title: "Perfil",
value: `${profileCompleteness}%`,
description: profileDescription,
icon: User
},
]);
} catch (err) {
console.error("Erro geral ao carregar dados do dashboard:", err);
setError("Não foi possível carregar as informações. Tente novamente mais tarde.");
} finally {
setIsLoading(false);
}
};
fetchData();
}, []);
if (isLoading) {
return <div className="text-center text-muted-foreground">Carregando dashboard...</div>;
}
if (error) {
return <div className="text-center text-destructive p-4 bg-destructive/10 rounded-md">{error}</div>;
}
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Dashboard</h1>
<p className="text-muted-foreground">Bem-vindo ao seu portal de consultas médicas.</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{statsData.map((stat) => (
<DashboardStatCard key={stat.title} {...stat} />
))}
</div>
<div className="grid md:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle>Ações Rápidas</CardTitle>
<CardDescription>Acesse rapidamente as principais funcionalidades.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{quickActions.map((action) => {
const Icon = action.icon;
return (
<Link key={action.href} href={action.href}>
<Button variant={action.variant} className="w-full justify-start bg-transparent">
<Icon className="mr-2 h-4 w-4" />
{action.label}
</Button>
</Link>
);
})}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Próximas Consultas</CardTitle>
<CardDescription>Suas consultas agendadas para o futuro.</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{upcomingAppointments.length > 0 ? (
upcomingAppointments.slice(0, 5).map((appointment) => (
<UpcomingAppointmentItem key={appointment.id} appointment={{
doctorName: appointment.doctors?.full_name || 'Médico a confirmar',
specialty: appointment.doctors?.specialty || 'Especialidade',
date: new Date(appointment.scheduled_at).toLocaleDateString('pt-BR', { day: '2-digit', month: 'short' }),
time: new Date(appointment.scheduled_at).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })
}} />
))
) : (
<p className="text-sm text-muted-foreground">Você não tem nenhuma consulta agendada.</p>
)}
</div>
</CardContent>
</Card>
</div>
</div>
);
}