integração da Api com os dashboards manager/secretary

This commit is contained in:
pedrosiimoes 2025-10-15 21:42:24 -03:00
parent bbce3eb932
commit edbe7ee87e
2 changed files with 351 additions and 83 deletions

View File

@ -1,41 +1,105 @@
import ManagerLayout from "@/components/manager-layout" "use client";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button" import ManagerLayout from "@/components/manager-layout";
import { Calendar, Clock, User, Plus } from "lucide-react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import Link from "next/link" import { Button } from "@/components/ui/button";
import { Calendar, Clock, Plus, User } from "lucide-react";
import Link from "next/link";
import React, { useState, useEffect } from "react";
import { usersService } from "services/usersApi.mjs";
import { doctorsService } from "services/doctorsApi.mjs";
export default function ManagerDashboard() { export default function ManagerDashboard() {
// 🔹 Estados para usuários
const [firstUser, setFirstUser] = useState<any>(null);
const [loadingUser, setLoadingUser] = useState(true);
// 🔹 Estados para médicos
const [doctors, setDoctors] = useState<any[]>([]);
const [loadingDoctors, setLoadingDoctors] = useState(true);
// 🔹 Buscar primeiro usuário
useEffect(() => {
async function fetchFirstUser() {
try {
const data = await usersService.list_roles();
if (Array.isArray(data) && data.length > 0) {
setFirstUser(data[0]);
}
} catch (error) {
console.error("Erro ao carregar usuário:", error);
} finally {
setLoadingUser(false);
}
}
fetchFirstUser();
}, []);
// 🔹 Buscar 3 primeiros médicos
useEffect(() => {
async function fetchDoctors() {
try {
const data = await doctorsService.list(); // ajuste se seu service tiver outro método
if (Array.isArray(data)) {
setDoctors(data.slice(0, 3)); // pega os 3 primeiros
}
} catch (error) {
console.error("Erro ao carregar médicos:", error);
} finally {
setLoadingDoctors(false);
}
}
fetchDoctors();
}, []);
return ( return (
<ManagerLayout> <ManagerLayout>
<div className="space-y-6"> <div className="space-y-6">
{/* Cabeçalho */}
<div> <div>
<h1 className="text-3xl font-bold text-gray-900">Dashboard</h1> <h1 className="text-3xl font-bold text-gray-900">Dashboard</h1>
<p className="text-gray-600">Bem-vindo ao seu portal de consultas médicas</p> <p className="text-gray-600">Bem-vindo ao seu portal de consultas médicas</p>
</div> </div>
{/* Cards principais */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{/* Card 1 */}
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Relatórios gerenciais</CardTitle> <CardTitle className="text-sm font-medium">Relatórios gerenciais</CardTitle>
<Calendar className="h-4 w-4 text-muted-foreground" /> <Calendar className="h-4 w-4 text-muted-foreground" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold">3</div> <div className="text-2xl font-bold">0</div>
<p className="text-xs text-muted-foreground">2 não lidos, 1 lido</p> <p className="text-xs text-muted-foreground">Relatórios disponíveis</p>
</CardContent> </CardContent>
</Card> </Card>
{/* Card 2 — Gestão de usuários */}
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Gestão de usuários</CardTitle> <CardTitle className="text-sm font-medium">Gestão de usuários</CardTitle>
<Clock className="h-4 w-4 text-muted-foreground" /> <Clock className="h-4 w-4 text-muted-foreground" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold">João Marques</div> {loadingUser ? (
<p className="text-xs text-muted-foreground">fez login a 13min</p> <div className="text-gray-500 text-sm">Carregando usuário...</div>
) : firstUser ? (
<>
<div className="text-2xl font-bold">{firstUser.full_name || "Sem nome"}</div>
<p className="text-xs text-muted-foreground">
{firstUser.email || "Sem e-mail cadastrado"}
</p>
</>
) : (
<div className="text-sm text-gray-500">Nenhum usuário encontrado</div>
)}
</CardContent> </CardContent>
</Card> </Card>
{/* Card 3 — Perfil */}
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Perfil</CardTitle> <CardTitle className="text-sm font-medium">Perfil</CardTitle>
@ -48,66 +112,79 @@ export default function ManagerDashboard() {
</Card> </Card>
</div> </div>
{/* Cards secundários */}
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
{/* Card — Ações rápidas */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Ações Rápidas</CardTitle> <CardTitle>Ações Rápidas</CardTitle>
<CardDescription>Acesse rapidamente as principais funcionalidades</CardDescription> <CardDescription>Acesse rapidamente as principais funcionalidades</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<Link href="##"> <Link href="/manager/home">
<Button className="w-full justify-start"> <Button className="w-full justify-start">
<Plus className="mr-2 h-4 w-4" /> <User className="mr-2 h-4 w-4" />
# Gestão de Médicos
</Button> </Button>
</Link> </Link>
<Link href="##"> <Link href="/manager/usuario">
<Button variant="outline" className="w-full justify-start bg-transparent">
<Calendar className="mr-2 h-4 w-4" />
#
</Button>
</Link>
<Link href="##">
<Button variant="outline" className="w-full justify-start bg-transparent"> <Button variant="outline" className="w-full justify-start bg-transparent">
<User className="mr-2 h-4 w-4" /> <User className="mr-2 h-4 w-4" />
# Usuários Cadastrados
</Button>
</Link>
<Link href="/manager/home/novo">
<Button variant="outline" className="w-full justify-start bg-transparent">
<Plus className="mr-2 h-4 w-4" />
Adicionar Novo Médico
</Button>
</Link>
<Link href="/manager/usuario/novo">
<Button variant="outline" className="w-full justify-start bg-transparent">
<Plus className="mr-2 h-4 w-4" />
Criar novo Usuário
</Button> </Button>
</Link> </Link>
</CardContent> </CardContent>
</Card> </Card>
{/* Card — Gestão de Médicos */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Gestão de Médicos</CardTitle> <CardTitle>Gestão de Médicos</CardTitle>
<CardDescription>Médicos online</CardDescription> <CardDescription>Médicos cadastrados recentemente</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-4"> {loadingDoctors ? (
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg"> <p className="text-sm text-gray-500">Carregando médicos...</p>
<div> ) : doctors.length === 0 ? (
<p className="font-medium">Dr. Silva</p> <p className="text-sm text-gray-500">Nenhum médico cadastrado.</p>
<p className="text-sm text-gray-600">Cardiologia</p> ) : (
</div> <div className="space-y-4">
<div className="text-right"> {doctors.map((doc, index) => (
<p className="font-medium">On-line</p> <div
<p className="text-sm text-gray-600"></p> key={index}
</div> className="flex items-center justify-between p-3 bg-green-50 rounded-lg border border-green-100"
>
<div>
<p className="font-medium">{doc.full_name || "Sem nome"}</p>
<p className="text-sm text-gray-600">
{doc.specialty || "Sem especialidade"}
</p>
</div>
<div className="text-right">
<p className="font-medium text-green-700">
{doc.active ? "Ativo" : "Inativo"}
</p>
</div>
</div>
))}
</div> </div>
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg"> )}
<div>
<p className="font-medium">Dra. Santos</p>
<p className="text-sm text-gray-600">Dermatologia</p>
</div>
<div className="text-right">
<p className="font-medium">Off-line</p>
<p className="text-sm text-gray-600">Visto as 8:33</p>
</div>
</div>
</div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
</div> </div>
</ManagerLayout> </ManagerLayout>
) );
} }

View File

@ -1,41 +1,207 @@
import SecretaryLayout from "@/components/secretary-layout" "use client";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button" import SecretaryLayout from "@/components/secretary-layout";
import { Calendar, Clock, User, Plus } from "lucide-react" import {
import Link from "next/link" Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Calendar, Clock, User, Plus } from "lucide-react";
import Link from "next/link";
import React, { useState, useEffect } from "react";
import { patientsService } from "@/services/patientsApi.mjs";
import { appointmentsService } from "@/services/appointmentsApi.mjs";
export default function SecretaryDashboard() { export default function SecretaryDashboard() {
// Estados
const [patients, setPatients] = useState<any[]>([]);
const [loadingPatients, setLoadingPatients] = useState(true);
const [firstConfirmed, setFirstConfirmed] = useState<any>(null);
const [nextAgendada, setNextAgendada] = useState<any>(null);
const [loadingAppointments, setLoadingAppointments] = useState(true);
// 🔹 Buscar pacientes
useEffect(() => {
async function fetchPatients() {
try {
const data = await patientsService.list();
if (Array.isArray(data)) {
setPatients(data.slice(0, 3));
}
} catch (error) {
console.error("Erro ao carregar pacientes:", error);
} finally {
setLoadingPatients(false);
}
}
fetchPatients();
}, []);
// 🔹 Buscar consultas (confirmadas + 1ª do mês)
useEffect(() => {
async function fetchAppointments() {
try {
const hoje = new Date();
const inicioMes = new Date(hoje.getFullYear(), hoje.getMonth(), 1);
const fimMes = new Date(hoje.getFullYear(), hoje.getMonth() + 1, 0);
// Mesmo parâmetro de ordenação da página /secretary/appointments
const queryParams = "order=scheduled_at.desc";
const data = await appointmentsService.search_appointment(queryParams);
if (!Array.isArray(data) || data.length === 0) {
setFirstConfirmed(null);
setNextAgendada(null);
return;
}
// 🩵 1⃣ Consultas confirmadas (para o card “Próxima Consulta Confirmada”)
const confirmadas = data.filter((apt: any) => {
const dataConsulta = new Date(apt.scheduled_at || apt.date);
return apt.status === "confirmed" && dataConsulta >= hoje;
});
confirmadas.sort(
(a: any, b: any) =>
new Date(a.scheduled_at || a.date).getTime() -
new Date(b.scheduled_at || b.date).getTime()
);
setFirstConfirmed(confirmadas[0] || null);
// 💙 2⃣ Consultas deste mês — pegar sempre a 1ª (mais próxima)
const consultasMes = data.filter((apt: any) => {
const dataConsulta = new Date(apt.scheduled_at);
return dataConsulta >= inicioMes && dataConsulta <= fimMes;
});
if (consultasMes.length > 0) {
consultasMes.sort(
(a: any, b: any) =>
new Date(a.scheduled_at).getTime() -
new Date(b.scheduled_at).getTime()
);
setNextAgendada(consultasMes[0]);
} else {
setNextAgendada(null);
}
} catch (error) {
console.error("Erro ao carregar consultas:", error);
} finally {
setLoadingAppointments(false);
}
}
fetchAppointments();
}, []);
return ( return (
<SecretaryLayout> <SecretaryLayout>
<div className="space-y-6"> <div className="space-y-6">
{/* Cabeçalho */}
<div> <div>
<h1 className="text-3xl font-bold text-gray-900">Dashboard</h1> <h1 className="text-3xl font-bold text-gray-900">Dashboard</h1>
<p className="text-gray-600">Bem-vindo ao seu portal de consultas médicas</p> <p className="text-gray-600">Bem-vindo ao seu portal de consultas médicas</p>
</div> </div>
{/* Cards principais */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{/* Próxima Consulta Confirmada */}
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Próxima Consulta</CardTitle> <CardTitle className="text-sm font-medium">
Próxima Consulta Confirmada
</CardTitle>
<Calendar className="h-4 w-4 text-muted-foreground" /> <Calendar className="h-4 w-4 text-muted-foreground" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold">15 Jan</div> {loadingAppointments ? (
<p className="text-xs text-muted-foreground">Dr. Silva - 14:30</p> <div className="text-gray-500 text-sm">
Carregando próxima consulta...
</div>
) : firstConfirmed ? (
<>
<div className="text-2xl font-bold">
{new Date(
firstConfirmed.scheduled_at || firstConfirmed.date
).toLocaleDateString("pt-BR")}
</div>
<p className="text-xs text-muted-foreground">
{firstConfirmed.doctor_name
? `Dr(a). ${firstConfirmed.doctor_name}`
: "Médico não informado"}{" "}
-{" "}
{new Date(
firstConfirmed.scheduled_at
).toLocaleTimeString("pt-BR", {
hour: "2-digit",
minute: "2-digit",
})}
</p>
</>
) : (
<div className="text-sm text-gray-500">
Nenhuma consulta confirmada encontrada
</div>
)}
</CardContent> </CardContent>
</Card> </Card>
{/* Consultas Este Mês */}
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Consultas Este Mês</CardTitle> <CardTitle className="text-sm font-medium">
Consultas Este Mês
</CardTitle>
<Clock className="h-4 w-4 text-muted-foreground" /> <Clock className="h-4 w-4 text-muted-foreground" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold">3</div> {loadingAppointments ? (
<p className="text-xs text-muted-foreground">2 realizadas, 1 agendada</p> <div className="text-gray-500 text-sm">
Carregando consultas...
</div>
) : nextAgendada ? (
<>
<div className="text-lg font-bold text-gray-900">
{new Date(
nextAgendada.scheduled_at
).toLocaleDateString("pt-BR", {
day: "2-digit",
month: "2-digit",
year: "numeric",
})}{" "}
às{" "}
{new Date(
nextAgendada.scheduled_at
).toLocaleTimeString("pt-BR", {
hour: "2-digit",
minute: "2-digit",
})}
</div>
<p className="text-xs text-muted-foreground">
{nextAgendada.doctor_name
? `Dr(a). ${nextAgendada.doctor_name}`
: "Médico não informado"}
</p>
<p className="text-xs text-muted-foreground">
{nextAgendada.patient_name
? `Paciente: ${nextAgendada.patient_name}`
: ""}
</p>
</>
) : (
<div className="text-sm text-gray-500">
Nenhuma consulta agendada neste mês
</div>
)}
</CardContent> </CardContent>
</Card> </Card>
{/* Perfil */}
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Perfil</CardTitle> <CardTitle className="text-sm font-medium">Perfil</CardTitle>
@ -48,11 +214,15 @@ export default function SecretaryDashboard() {
</Card> </Card>
</div> </div>
{/* Cards Secundários */}
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
{/* Ações rápidas */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Ações Rápidas</CardTitle> <CardTitle>Ações Rápidas</CardTitle>
<CardDescription>Acesse rapidamente as principais funcionalidades</CardDescription> <CardDescription>
Acesse rapidamente as principais funcionalidades
</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<Link href="/secretary/schedule"> <Link href="/secretary/schedule">
@ -62,52 +232,73 @@ export default function SecretaryDashboard() {
</Button> </Button>
</Link> </Link>
<Link href="/secretary/appointments"> <Link href="/secretary/appointments">
<Button variant="outline" className="w-full justify-start bg-transparent"> <Button
variant="outline"
className="w-full justify-start bg-transparent"
>
<Calendar className="mr-2 h-4 w-4" /> <Calendar className="mr-2 h-4 w-4" />
Ver Consultas Ver Consultas
</Button> </Button>
</Link> </Link>
<Link href="##"> <Link href="/secretary/pacientes">
<Button variant="outline" className="w-full justify-start bg-transparent"> <Button
variant="outline"
className="w-full justify-start bg-transparent"
>
<User className="mr-2 h-4 w-4" /> <User className="mr-2 h-4 w-4" />
Atualizar Dados Gerenciar Pacientes
</Button> </Button>
</Link> </Link>
</CardContent> </CardContent>
</Card> </Card>
{/* Pacientes */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Próximas Consultas</CardTitle> <CardTitle>Pacientes</CardTitle>
<CardDescription>Suas consultas agendadas</CardDescription> <CardDescription>
Últimos pacientes cadastrados
</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-4"> {loadingPatients ? (
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg"> <p className="text-sm text-gray-500">
<div> Carregando pacientes...
<p className="font-medium">Dr. Silva</p> </p>
<p className="text-sm text-gray-600">Cardiologia</p> ) : patients.length === 0 ? (
</div> <p className="text-sm text-gray-500">
<div className="text-right"> Nenhum paciente cadastrado.
<p className="font-medium">15 Jan</p> </p>
<p className="text-sm text-gray-600">14:30</p> ) : (
</div> <div className="space-y-4">
{patients.map((patient, index) => (
<div
key={index}
className="flex items-center justify-between p-3 bg-blue-50 rounded-lg border border-blue-100"
>
<div>
<p className="font-medium text-gray-900">
{patient.full_name || "Sem nome"}
</p>
<p className="text-sm text-gray-600">
{patient.phone_mobile ||
patient.phone1 ||
"Sem telefone"}
</p>
</div>
<div className="text-right">
<p className="font-medium text-blue-700">
{patient.convenio || "Particular"}
</p>
</div>
</div>
))}
</div> </div>
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg"> )}
<div>
<p className="font-medium">Dra. Santos</p>
<p className="text-sm text-gray-600">Dermatologia</p>
</div>
<div className="text-right">
<p className="font-medium">22 Jan</p>
<p className="text-sm text-gray-600">10:00</p>
</div>
</div>
</div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
</div> </div>
</SecretaryLayout> </SecretaryLayout>
) );
} }