guisilvagomes e7aa76df75 Atualizar
2025-10-12 20:18:58 -03:00

995 lines
35 KiB
TypeScript

import React, { useState, useEffect, useCallback } from "react";
import {
Calendar,
Clock,
TrendingUp,
Video,
MapPin,
Phone,
FileText,
Settings,
LogOut,
Home,
CheckCircle,
AlertCircle,
XCircle,
HelpCircle,
Plus,
Edit,
Trash2,
} from "lucide-react";
import toast from "react-hot-toast";
import { format } from "date-fns";
import { ptBR } from "date-fns/locale";
import { useNavigate } from "react-router-dom";
import { Consulta as ServiceConsulta } from "../services/consultasService";
import { listPatients } from "../services/pacienteService";
import { useAuth } from "../hooks/useAuth";
import relatorioService, {
RelatorioCreate,
} from "../services/relatorioService";
import ConsultaModal from "../components/consultas/ConsultaModal";
import AvailabilityManager from "../components/agenda/AvailabilityManager";
import ExceptionsManager from "../components/agenda/ExceptionsManager";
interface ConsultaUI {
id: string;
pacienteId: string;
medicoId: string;
pacienteNome: string;
medicoNome: string;
dataHora: string;
status: string;
tipo?: string;
observacoes?: string;
}
interface Paciente {
_id: string;
nome: string;
telefone: string;
email: string;
convenio: string;
observacoes: string;
}
const PainelMedico: React.FC = () => {
const { user, roles, logout } = useAuth();
const navigate = useNavigate();
// Auth
const temAcessoMedico =
user &&
(user.role === "medico" ||
roles.includes("medico") ||
roles.includes("admin"));
const medicoId = temAcessoMedico ? user.id : "";
const medicoNome = user?.nome || "Médico";
// State
const [activeTab, setActiveTab] = useState("dashboard");
const [consultas, setConsultas] = useState<ConsultaUI[]>([]);
const [filtroData, setFiltroData] = useState("hoje");
const [loading, setLoading] = useState(true);
const [modalOpen, setModalOpen] = useState(false);
const [editing, setEditing] = useState<ConsultaUI | null>(null);
const [relatorioModalOpen, setRelatorioModalOpen] = useState(false);
const [loadingRelatorio, setLoadingRelatorio] = useState(false);
const [pacientesDisponiveis, setPacientesDisponiveis] = useState<
Array<{ id: string; nome: string }>
>([]);
const [formRelatorio, setFormRelatorio] = useState({
patient_id: "",
order_number: "",
exam: "",
diagnosis: "",
conclusion: "",
cid_code: "",
content_html: "",
status: "draft" as "draft" | "pending" | "completed" | "cancelled",
requested_by: medicoNome,
due_at: format(new Date(), "yyyy-MM-dd'T'HH:mm"),
hide_date: false,
hide_signature: false,
});
useEffect(() => {
if (!medicoId) navigate("/login-medico");
}, [medicoId, navigate]);
const fetchConsultas = useCallback(async () => {
if (!medicoId) return;
setLoading(true);
try {
const raw = localStorage.getItem("consultas_local");
let lista: ServiceConsulta[] = [];
if (raw) {
try {
lista = JSON.parse(raw);
} catch {
lista = [];
}
}
let filtradas = lista.filter((c) => c.medicoId === medicoId);
const hoje = new Date();
if (filtroData === "hoje") {
const dStr = format(hoje, "yyyy-MM-dd");
filtradas = filtradas.filter((c) => c.dataHora.startsWith(dStr));
} else if (filtroData === "amanha") {
const amanha = new Date(hoje);
amanha.setDate(hoje.getDate() + 1);
const dStr = format(amanha, "yyyy-MM-dd");
filtradas = filtradas.filter((c) => c.dataHora.startsWith(dStr));
} else if (filtroData === "semana") {
const start = new Date(hoje);
start.setDate(hoje.getDate() - hoje.getDay());
const end = new Date(start);
end.setDate(start.getDate() + 6);
filtradas = filtradas.filter((c) => {
const d = new Date(c.dataHora);
return d >= start && d <= end;
});
}
const pacientesResponse = await listPatients({ per_page: 200 }).catch(
() => ({ data: [], total: 0, page: 1, per_page: 0 })
);
const pacMap: Record<string, Paciente> = {};
const pacientesLista =
"data" in pacientesResponse ? pacientesResponse.data : [];
pacientesLista.forEach((p) => {
pacMap[p.id] = {
_id: p.id,
nome: p.nome,
telefone: p.telefone || "",
email: p.email || "",
convenio: p.convenio || "",
observacoes: p.observacoes || "",
};
});
setConsultas(
filtradas.map((c) => ({
id: c.id,
pacienteId: c.pacienteId,
medicoId: c.medicoId,
pacienteNome: pacMap[c.pacienteId]?.nome || c.pacienteId,
medicoNome: medicoNome,
dataHora: c.dataHora,
status: c.status,
tipo: c.tipo,
observacoes: c.observacoes,
}))
);
} finally {
setLoading(false);
}
}, [medicoId, filtroData, medicoNome]);
useEffect(() => {
fetchConsultas();
}, [fetchConsultas]);
useEffect(() => {
if (relatorioModalOpen && user?.id) {
const carregarPacientes = async () => {
try {
const response = await listPatients({ per_page: 200 });
if ("data" in response) {
setPacientesDisponiveis(
response.data.map((p) => ({
id: p.id,
nome: p.nome,
}))
);
}
} catch (error) {
console.error("Erro ao carregar pacientes:", error);
toast.error("Erro ao carregar lista de pacientes");
}
};
carregarPacientes();
}
}, [relatorioModalOpen, user]);
const handleCriarRelatorio = async (e: React.FormEvent) => {
e.preventDefault();
if (!formRelatorio.patient_id) {
toast.error("Selecione um paciente");
return;
}
if (!formRelatorio.exam.trim()) {
toast.error("Informe o tipo de exame");
return;
}
setLoadingRelatorio(true);
try {
const payload: RelatorioCreate = {
patient_id: formRelatorio.patient_id,
order_number: formRelatorio.order_number || "",
exam: formRelatorio.exam,
diagnosis: formRelatorio.diagnosis || "",
conclusion: formRelatorio.conclusion || "",
cid_code: formRelatorio.cid_code || "",
content_html: formRelatorio.content_html || "",
status: formRelatorio.status,
requested_by: formRelatorio.requested_by || medicoNome,
due_at: formRelatorio.due_at || "",
hide_date: formRelatorio.hide_date,
hide_signature: formRelatorio.hide_signature,
};
const resp = await relatorioService.criarRelatorio(payload);
if (resp.success) {
toast.success("Relatório criado com sucesso!");
setRelatorioModalOpen(false);
setFormRelatorio({
patient_id: "",
order_number: "",
exam: "",
diagnosis: "",
conclusion: "",
cid_code: "",
content_html: "",
status: "draft",
requested_by: medicoNome,
due_at: format(new Date(), "yyyy-MM-dd'T'HH:mm"),
hide_date: false,
hide_signature: false,
});
} else {
toast.error(resp.error || "Erro ao criar relatório");
}
} catch (error) {
console.error("Erro ao criar relatório:", error);
toast.error("Erro ao criar relatório");
} finally {
setLoadingRelatorio(false);
}
};
const handleNovaConsulta = () => {
setEditing(null);
setModalOpen(true);
};
const handleEditConsulta = (consulta: ConsultaUI) => {
setEditing(consulta);
setModalOpen(true);
};
const handleDeleteConsulta = async (id: string) => {
if (!window.confirm("Deseja realmente excluir esta consulta?")) return;
try {
const raw = localStorage.getItem("consultas_local");
if (raw) {
const lista: ServiceConsulta[] = JSON.parse(raw);
const nova = lista.filter((c) => c.id !== id);
localStorage.setItem("consultas_local", JSON.stringify(nova));
toast.success("Consulta excluída");
fetchConsultas();
}
} catch (error) {
console.error("Erro ao excluir consulta:", error);
toast.error("Erro ao excluir consulta");
}
};
const handleSaveConsulta = () => {
setModalOpen(false);
setEditing(null);
fetchConsultas();
};
const getStatusColor = (status: string) => {
switch (status.toLowerCase()) {
case "confirmada":
case "confirmed":
return "bg-green-100 text-green-800 border-green-200 dark:bg-green-900/30 dark:text-green-300 dark:border-green-800";
case "agendada":
case "scheduled":
return "bg-indigo-100 text-indigo-800 border-blue-200 dark:bg-indigo-900/30 dark:text-blue-300 dark:border-indigo-800";
case "concluida":
case "completed":
return "bg-gray-100 text-gray-800 border-gray-200 dark:bg-gray-900/30 dark:text-gray-300 dark:border-gray-800";
case "cancelada":
case "cancelled":
return "bg-red-100 text-red-800 border-red-200 dark:bg-red-900/30 dark:text-red-300 dark:border-red-800";
default:
return "bg-gray-100 text-gray-800 border-gray-200 dark:bg-gray-900/30 dark:text-gray-300 dark:border-gray-800";
}
};
const getStatusLabel = (status: string) => {
switch (status.toLowerCase()) {
case "confirmada":
case "confirmed":
return "Confirmada";
case "agendada":
case "scheduled":
return "Agendada";
case "concluida":
case "completed":
return "Concluída";
case "cancelada":
case "cancelled":
return "Cancelada";
default:
return status;
}
};
const getStatusIcon = (status: string) => {
switch (status.toLowerCase()) {
case "confirmada":
case "confirmed":
return <CheckCircle className="h-4 w-4" />;
case "agendada":
case "scheduled":
return <Clock className="h-4 w-4" />;
case "cancelada":
case "cancelled":
return <XCircle className="h-4 w-4" />;
default:
return <AlertCircle className="h-4 w-4" />;
}
};
// Stats
const consultasHoje = consultas.filter((c) =>
c.dataHora.startsWith(format(new Date(), "yyyy-MM-dd"))
);
const consultasConfirmadas = consultas.filter(
(c) =>
c.status.toLowerCase() === "confirmada" ||
c.status.toLowerCase() === "confirmed"
);
const consultasConcluidas = consultas.filter(
(c) =>
c.status.toLowerCase() === "concluida" ||
c.status.toLowerCase() === "completed"
);
// Sidebar
const menuItems = [
{ id: "dashboard", label: "Dashboard", icon: Home },
{ id: "appointments", label: "Consultas", icon: Clock },
{ id: "availability", label: "Disponibilidade", icon: Calendar },
{ id: "reports", label: "Relatórios", icon: FileText },
{ id: "help", label: "Ajuda", icon: HelpCircle },
{ id: "settings", label: "Configurações", icon: Settings },
];
const renderSidebar = () => (
<div className="w-64 h-screen bg-white dark:bg-slate-900 border-r border-gray-200 dark:border-slate-700 flex flex-col">
{/* Doctor Profile */}
<div className="p-6 border-b border-gray-200 dark:border-slate-700">
<div className="flex items-center gap-3">
<div className="h-12 w-12 rounded-full bg-gradient-to-br from-indigo-500 to-indigo-600 flex items-center justify-center text-white font-semibold text-lg">
{medicoNome
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2)}
</div>
<div>
<p className="font-medium text-gray-900 dark:text-white">
{medicoNome}
</p>
<p className="text-sm text-gray-600 dark:text-gray-400">Médico</p>
</div>
</div>
</div>
{/* Navigation */}
<nav className="flex-1 p-4">
<div className="space-y-1">
{menuItems.map((item) => {
const Icon = item.icon;
const isActive = activeTab === item.id;
return (
<button
key={item.id}
onClick={() => {
if (item.id === "help") {
navigate("/ajuda");
} else {
setActiveTab(item.id);
}
}}
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 ${
isActive
? "bg-indigo-50 dark:bg-indigo-900/20 text-indigo-600 dark:text-indigo-400"
: "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-slate-800"
}`}
>
<Icon className="h-5 w-5" />
{item.label}
</button>
);
})}
</div>
</nav>
{/* Logout */}
<div className="p-4 border-t border-gray-200 dark:border-slate-700">
<button
onClick={() => {
logout();
navigate("/login-medico");
}}
className="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
>
<LogOut className="h-5 w-5" />
Sair
</button>
</div>
</div>
);
// Stats Cards
const renderStatCard = (
title: string,
value: string | number,
icon: React.ElementType,
description?: string,
trend?: string
) => {
const Icon = icon;
return (
<div className="bg-white dark:bg-slate-900 rounded-lg border border-gray-200 dark:border-slate-700 p-6">
<div className="flex items-center justify-between mb-4">
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">
{title}
</p>
<Icon className="h-5 w-5 text-gray-400 dark:text-gray-500" />
</div>
<div className="text-2xl font-bold text-gray-900 dark:text-white mb-1">
{value}
</div>
{description && (
<p className="text-sm text-gray-600 dark:text-gray-400">
{description}
</p>
)}
{trend && (
<p className="text-sm text-gray-600 dark:text-gray-400">{trend}</p>
)}
</div>
);
};
// Appointment Card
const renderAppointmentCard = (consulta: ConsultaUI) => (
<div
key={consulta.id}
className="flex items-start gap-4 p-4 rounded-lg border border-gray-200 dark:border-slate-700 hover:bg-gray-50 dark:hover:bg-slate-800/50 transition-colors"
>
<div className="h-12 w-12 rounded-full bg-gradient-to-br from-purple-500 to-purple-600 flex items-center justify-center text-white font-semibold">
{consulta.pacienteNome
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2)}
</div>
<div className="flex-1 space-y-2">
<div className="flex items-start justify-between">
<div>
<p className="font-medium text-gray-900 dark:text-white">
{consulta.pacienteNome}
</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
{consulta.observacoes || "Consulta médica"}
</p>
</div>
<div
className={`flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium border ${getStatusColor(
consulta.status
)}`}
>
{getStatusIcon(consulta.status)}
{getStatusLabel(consulta.status)}
</div>
</div>
<div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-1">
<Clock className="h-4 w-4" />
<span>
{format(new Date(consulta.dataHora), "HH:mm", { locale: ptBR })}
</span>
</div>
<div className="flex items-center gap-1">
{consulta.tipo === "online" || consulta.tipo === "telemedicina" ? (
<>
<Video className="h-4 w-4" />
<span>Online</span>
</>
) : (
<>
<MapPin className="h-4 w-4" />
<span>Presencial</span>
</>
)}
</div>
</div>
<div className="flex gap-2">
{consulta.status.toLowerCase() === "confirmada" && (
<>
<button className="flex items-center gap-1 px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-slate-800 border border-gray-300 dark:border-slate-600 rounded-md hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500">
<Phone className="h-4 w-4" />
Ligar
</button>
{(consulta.tipo === "online" ||
consulta.tipo === "telemedicina") && (
<button className="flex items-center gap-1 px-3 py-1 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-700 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500">
<Video className="h-4 w-4" />
Iniciar Consulta
</button>
)}
</>
)}
<button
onClick={() => handleEditConsulta(consulta)}
className="flex items-center gap-1 px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
>
<Edit className="h-4 w-4" />
Editar
</button>
<button
onClick={() => handleDeleteConsulta(consulta.id)}
className="flex items-center gap-1 px-3 py-1 text-sm font-medium text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500"
>
<Trash2 className="h-4 w-4" />
Excluir
</button>
</div>
</div>
</div>
);
// Content Sections
const renderDashboard = () => (
<div className="space-y-6">
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{renderStatCard(
"Consultas Hoje",
consultasHoje.length,
Clock,
`${consultasConfirmadas.length} confirmadas`
)}
{renderStatCard(
"Total Consultas",
consultas.length,
Calendar,
"Este período"
)}
{renderStatCard(
"Concluídas",
consultasConcluidas.length,
CheckCircle,
"Este período"
)}
{renderStatCard(
"Taxa Comparecimento",
consultas.length > 0
? `${Math.round(
(consultasConcluidas.length / consultas.length) * 100
)}%`
: "0%",
TrendingUp,
"Baseado em consultas concluídas"
)}
</div>
{/* Today's Appointments */}
<div className="bg-white dark:bg-slate-900 rounded-lg border border-gray-200 dark:border-slate-700">
<div className="p-6 border-b border-gray-200 dark:border-slate-700">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
Consultas de Hoje
</h2>
<button
onClick={handleNovaConsulta}
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-700 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
>
<Plus className="h-4 w-4" />
Nova Consulta
</button>
</div>
</div>
<div className="p-6">
{loading ? (
<div className="text-center py-8">
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-indigo-600 border-r-transparent"></div>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
Carregando consultas...
</p>
</div>
) : consultasHoje.length === 0 ? (
<p className="text-center py-8 text-gray-600 dark:text-gray-400">
Nenhuma consulta agendada para hoje
</p>
) : (
<div className="space-y-4">
{consultasHoje.map(renderAppointmentCard)}
</div>
)}
</div>
</div>
{/* Quick Stats */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-white dark:bg-slate-900 rounded-lg border border-gray-200 dark:border-slate-700">
<div className="p-6 border-b border-gray-200 dark:border-slate-700">
<h3 className="font-semibold text-gray-900 dark:text-white">
Próximos 7 Dias
</h3>
</div>
<div className="p-6">
<div className="space-y-3">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600 dark:text-gray-400">
Segunda-feira
</span>
<span className="text-sm font-medium text-gray-900 dark:text-white">
0 consultas
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600 dark:text-gray-400">
Terça-feira
</span>
<span className="text-sm font-medium text-gray-900 dark:text-white">
0 consultas
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600 dark:text-gray-400">
Quarta-feira
</span>
<span className="text-sm font-medium text-gray-900 dark:text-white">
0 consultas
</span>
</div>
</div>
</div>
</div>
<div className="bg-white dark:bg-slate-900 rounded-lg border border-gray-200 dark:border-slate-700">
<div className="p-6 border-b border-gray-200 dark:border-slate-700">
<h3 className="font-semibold text-gray-900 dark:text-white">
Tipos de Consulta
</h3>
</div>
<div className="p-6">
<div className="space-y-3">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600 dark:text-gray-400">
Presencial
</span>
<span className="text-sm font-medium text-gray-900 dark:text-white">
{
consultas.filter(
(c) => c.tipo !== "online" && c.tipo !== "telemedicina"
).length
}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600 dark:text-gray-400">
Online
</span>
<span className="text-sm font-medium text-gray-900 dark:text-white">
{
consultas.filter(
(c) => c.tipo === "online" || c.tipo === "telemedicina"
).length
}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
);
const renderAppointments = () => (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Todas as Consultas
</h1>
<button
onClick={handleNovaConsulta}
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-700 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
>
<Plus className="h-4 w-4" />
Nova Consulta
</button>
</div>
{/* Filters */}
<div className="flex gap-2">
{["hoje", "amanha", "semana", "todas"].map((filtro) => (
<button
key={filtro}
onClick={() => setFiltroData(filtro)}
className={`px-4 py-2 text-sm font-medium rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 ${
filtroData === filtro
? "bg-indigo-600 text-white"
: "bg-white dark:bg-slate-900 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-slate-700 hover:bg-gray-50 dark:hover:bg-slate-800"
}`}
>
{filtro === "hoje"
? "Hoje"
: filtro === "amanha"
? "Amanhã"
: filtro === "semana"
? "Esta Semana"
: "Todas"}
</button>
))}
</div>
<div className="bg-white dark:bg-slate-900 rounded-lg border border-gray-200 dark:border-slate-700">
<div className="p-6">
{loading ? (
<div className="text-center py-8">
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-indigo-600 border-r-transparent"></div>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
Carregando consultas...
</p>
</div>
) : consultas.length === 0 ? (
<p className="text-center py-8 text-gray-600 dark:text-gray-400">
Nenhuma consulta encontrada
</p>
) : (
<div className="space-y-4">
{consultas.map(renderAppointmentCard)}
</div>
)}
</div>
</div>
</div>
);
const renderAvailability = () => (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Gerenciar Disponibilidade
</h1>
<div className="bg-white dark:bg-slate-900 rounded-lg border border-gray-200 dark:border-slate-700 p-6">
<AvailabilityManager doctorId={medicoId} />
</div>
<div className="bg-white dark:bg-slate-900 rounded-lg border border-gray-200 dark:border-slate-700 p-6">
<ExceptionsManager doctorId={medicoId} />
</div>
</div>
);
const renderReports = () => (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Relatórios
</h1>
<button
onClick={() => setRelatorioModalOpen(true)}
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-700 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
>
<Plus className="h-4 w-4" />
Novo Relatório
</button>
</div>
<div className="bg-white dark:bg-slate-900 rounded-lg border border-gray-200 dark:border-slate-700">
<div className="p-6">
<p className="text-center py-8 text-gray-600 dark:text-gray-400">
Funcionalidade em desenvolvimento
</p>
</div>
</div>
</div>
);
const renderSettings = () => (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Configurações
</h1>
<div className="bg-white dark:bg-slate-900 rounded-lg border border-gray-200 dark:border-slate-700">
<div className="p-6">
<p className="text-center py-8 text-gray-600 dark:text-gray-400">
Funcionalidade em desenvolvimento
</p>
</div>
</div>
</div>
);
const renderContent = () => {
switch (activeTab) {
case "dashboard":
return renderDashboard();
case "appointments":
return renderAppointments();
case "availability":
return renderAvailability();
case "reports":
return renderReports();
case "settings":
return renderSettings();
default:
return null;
}
};
if (!temAcessoMedico) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
Acesso Negado
</h1>
<p className="text-gray-600 dark:text-gray-400 mb-6">
Você não tem permissão para acessar esta página.
</p>
<button
onClick={() => navigate("/login-medico")}
className="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-700 transition-colors"
>
Fazer Login
</button>
</div>
</div>
);
}
return (
<div className="flex h-screen bg-gray-50 dark:bg-slate-950">
{renderSidebar()}
<main className="flex-1 overflow-y-auto">
<div className="container mx-auto p-8">{renderContent()}</div>
</main>
{/* Modals */}
{modalOpen && (
<ConsultaModal
open={modalOpen}
onClose={() => {
setModalOpen(false);
setEditing(null);
}}
onSave={handleSaveConsulta}
initialData={
editing
? {
id: editing.id,
pacienteId: editing.pacienteId,
medicoId: editing.medicoId,
dataHora: editing.dataHora,
status: editing.status,
tipo: editing.tipo,
observacoes: editing.observacoes,
}
: undefined
}
doctorId={medicoId}
doctorName={medicoNome}
/>
)}
{relatorioModalOpen && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
onClick={() => setRelatorioModalOpen(false)}
>
<div
className="bg-white dark:bg-slate-900 rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6 border-b border-gray-200 dark:border-slate-700">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Criar Novo Relatório
</h2>
</div>
<form onSubmit={handleCriarRelatorio} className="p-6 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Paciente *
</label>
<select
value={formRelatorio.patient_id}
onChange={(e) =>
setFormRelatorio((p) => ({
...p,
patient_id: e.target.value,
}))
}
required
className="w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-md bg-white dark:bg-slate-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
>
<option value="">Selecione um paciente</option>
{pacientesDisponiveis.map((p) => (
<option key={p.id} value={p.id}>
{p.nome}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Tipo de Exame *
</label>
<input
type="text"
value={formRelatorio.exam}
onChange={(e) =>
setFormRelatorio((p) => ({ ...p, exam: e.target.value }))
}
required
className="w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-md bg-white dark:bg-slate-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Diagnóstico
</label>
<textarea
value={formRelatorio.diagnosis}
onChange={(e) =>
setFormRelatorio((p) => ({
...p,
diagnosis: e.target.value,
}))
}
rows={3}
className="w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-md bg-white dark:bg-slate-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Conclusão
</label>
<textarea
value={formRelatorio.conclusion}
onChange={(e) =>
setFormRelatorio((p) => ({
...p,
conclusion: e.target.value,
}))
}
rows={3}
className="w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-md bg-white dark:bg-slate-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</div>
<div className="flex justify-end gap-3 pt-4">
<button
type="button"
onClick={() => setRelatorioModalOpen(false)}
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-slate-800 border border-gray-300 dark:border-slate-600 rounded-md hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
>
Cancelar
</button>
<button
type="submit"
disabled={loadingRelatorio}
className="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
>
{loadingRelatorio ? "Criando..." : "Criar Relatório"}
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
};
export default PainelMedico;