This commit is contained in:
Jhony 2025-11-26 22:28:00 -03:00
parent b9b49cba42
commit 8a63219cf6
7 changed files with 318 additions and 557 deletions

View File

@ -106,10 +106,6 @@ export default function NovoLaudoPage() {
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="order_number"> do Pedido</Label>
<Input id="order_number" value={formData.order_number} onChange={handleInputChange} />
</div>
<div className="space-y-2">
<Label htmlFor="exam">Exame</Label>
<Input id="exam" value={formData.exam} onChange={handleInputChange} />

View File

@ -3,7 +3,7 @@
import { useEffect, useState, useCallback } from "react";
import Link from "next/link";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Eye, Edit, Calendar, Trash2, Loader2 } from "lucide-react";
import { Eye, Edit, Calendar, Trash2, Loader2, MoreVertical } from "lucide-react";
import { api } from "@/services/api.mjs";
import { PatientDetailsModal } from "@/components/ui/patient-details-modal";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@ -185,7 +185,7 @@ export default function PacientesPage() {
<SelectItem value="20">20 por página</SelectItem>
</SelectContent>
</Select>
<Link href="/doctor/pacientes/novo" className="w-full sm:w-auto">
<Link href="/doctor/medicos/novo" className="w-full sm:w-auto">
<Button variant="default" className="bg-green-600 hover:bg-green-700 w-full sm:w-auto">
Novo Paciente
</Button>
@ -262,9 +262,10 @@ export default function PacientesPage() {
<td className="p-3 sm:p-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="text-primary hover:underline text-sm sm:text-base">
Ações
</button>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Abrir menu</span>
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleOpenModal(p)}>
@ -272,16 +273,16 @@ export default function PacientesPage() {
Ver detalhes
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/doctor/pacientes/${p.id}/laudos`}>
<Link href={`/doctor/medicos/${p.id}/laudos`}>
<Edit className="w-4 h-4 mr-2" />
Laudos
</Link>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert(`Agenda para paciente ID: ${p.id}`)}>
{/* <DropdownMenuItem onClick={() => alert(`Agenda para paciente ID: ${p.id}`)}>
<Calendar className="w-4 h-4 mr-2" />
Ver agenda
</DropdownMenuItem>
<DropdownMenuItem
</DropdownMenuItem> */}
{/* <DropdownMenuItem
onClick={() => {
const newPacientes = pacientes.filter((pac) => pac.id !== p.id);
setPacientes(newPacientes);
@ -291,7 +292,7 @@ export default function PacientesPage() {
>
<Trash2 className="w-4 h-4 mr-2" />
Excluir
</DropdownMenuItem>
</DropdownMenuItem> */}
</DropdownMenuContent>
</DropdownMenu>
</td>
@ -387,11 +388,10 @@ export default function PacientesPage() {
<button
key={number}
onClick={() => paginate(number)}
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm border border-border ${
currentPage === number
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm border border-border ${currentPage === number
? "bg-green-600 text-primary-foreground shadow-md border-green-600"
: "bg-secondary text-secondary-foreground hover:bg-secondary/80"
}`}
}`}
>
{number}
</button>
@ -411,11 +411,11 @@ export default function PacientesPage() {
</div>
</div>
<PatientDetailsModal
patient={selectedPatient}
isOpen={isModalOpen}
onClose={handleCloseModal}
/>
</Sidebar>
);
<PatientDetailsModal
patient={selectedPatient}
isOpen={isModalOpen}
onClose={handleCloseModal}
/>
</Sidebar>
);
}

View File

@ -6,7 +6,7 @@ import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react"
import { Edit, Trash2, Eye, Calendar, Filter, Loader2, MoreVertical } from "lucide-react"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"
import { doctorsService } from "services/doctorsApi.mjs";
@ -284,7 +284,10 @@ export default function DoctorsPage() {
<td className="px-4 py-3 text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="text-blue-600 cursor-pointer inline-block">Ações</div>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Abrir menu</span>
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(doctor)}>
@ -342,7 +345,7 @@ export default function DoctorsPage() {
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="text-blue-600 cursor-pointer inline-block">Ações</div>
<div className="text-blue-600 cursor-pointer inline-block">A99ções</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(doctor)}>
@ -386,11 +389,10 @@ export default function DoctorsPage() {
<button
key={number}
onClick={() => paginate(number)}
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm border border-gray-300 ${
currentPage === number
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm border border-gray-300 ${currentPage === number
? "bg-green-600 text-white shadow-md border-green-600"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
}`}
>
{number}
</button>

View File

@ -5,7 +5,7 @@ import Link from "next/link";
import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react";
import { Edit, Trash2, Eye, Calendar, Filter, Loader2, MoreVertical } from "lucide-react";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import { patientsService } from "@/services/patientsApi.mjs";
import Sidebar from "@/components/Sidebar";
@ -264,7 +264,10 @@ export default function PacientesPage() {
<td className="p-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="text-blue-600 cursor-pointer">Ações</div>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Abrir menu</span>
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>
@ -321,38 +324,41 @@ export default function PacientesPage() {
{patient.nome}
{patient.vip && (
<span className="ml-2 px-2 py-0.5 text-xs font-semibold text-purple-600 bg-purple-100 rounded-full">VIP</span>
)}
)}
</div>
<div className="text-sm text-gray-600">Telefone: {patient.telefone}</div>
<div className="text-sm text-gray-600">Convênio: {patient.convenio}</div>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="w-full"><Button variant="outline" className="w-full">Ações</Button></div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>
<Eye className="w-4 h-4 mr-2" />
Ver detalhes
</DropdownMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Abrir menu</span>
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>
<Eye className="w-4 h-4 mr-2" />
Ver detalhes
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/secretary/pacientes/${patient.id}/editar`} className="flex items-center w-full">
<Edit className="w-4 h-4 mr-2" />
Editar
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/secretary/pacientes/${patient.id}/editar`} className="flex items-center w-full">
<Edit className="w-4 h-4 mr-2" />
Editar
</Link>
</DropdownMenuItem>
<DropdownMenuItem>
<Calendar className="w-4 h-4 mr-2" />
Marcar consulta
</DropdownMenuItem>
<DropdownMenuItem className="text-red-600" onClick={() => openDeleteDialog(String(patient.id))}>
<Trash2 className="w-4 h-4 mr-2" />
Excluir
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenuItem>
<Calendar className="w-4 h-4 mr-2" />
Marcar consulta
</DropdownMenuItem>
<DropdownMenuItem className="text-red-600" onClick={() => openDeleteDialog(String(patient.id))}>
<Trash2 className="w-4 h-4 mr-2" />
Excluir
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
))}
</div>
@ -430,65 +436,65 @@ export default function PacientesPage() {
<div className="grid gap-4 py-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<p className="font-semibold">Nome Completo</p>
<p>{patientDetails.full_name}</p>
<p className="font-semibold">Nome Completo</p>
<p>{patientDetails.full_name}</p>
</div>
<div>
<p className="font-semibold">Email</p>
<p>{patientDetails.email}</p>
<p className="font-semibold">Email</p>
<p>{patientDetails.email}</p>
</div>
<div>
<p className="font-semibold">Telefone</p>
<p>{patientDetails.phone_mobile}</p>
<p className="font-semibold">Telefone</p>
<p>{patientDetails.phone_mobile}</p>
</div>
<div>
<p className="font-semibold">Data de Nascimento</p>
<p>{patientDetails.birth_date}</p>
<p className="font-semibold">Data de Nascimento</p>
<p>{patientDetails.birth_date}</p>
</div>
<div>
<p className="font-semibold">CPF</p>
<p>{patientDetails.cpf}</p>
<p className="font-semibold">CPF</p>
<p>{patientDetails.cpf}</p>
</div>
<div>
<p className="font-semibold">Tipo Sanguíneo</p>
<p>{patientDetails.blood_type}</p>
<p className="font-semibold">Tipo Sanguíneo</p>
<p>{patientDetails.blood_type}</p>
</div>
<div>
<p className="font-semibold">Peso (kg)</p>
<p>{patientDetails.weight_kg}</p>
<p className="font-semibold">Peso (kg)</p>
<p>{patientDetails.weight_kg}</p>
</div>
<div>
<p className="font-semibold">Altura (m)</p>
<p>{patientDetails.height_m}</p>
<p className="font-semibold">Altura (m)</p>
<p>{patientDetails.height_m}</p>
</div>
</div>
<div className="border-t pt-4 mt-4">
<h3 className="font-semibold mb-2">Endereço</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<p className="font-semibold">Rua</p>
<p>{`${patientDetails.street}, ${patientDetails.number}`}</p>
</div>
<div>
<p className="font-semibold">Complemento</p>
<p>{patientDetails.complement}</p>
</div>
<div>
<p className="font-semibold">Bairro</p>
<p>{patientDetails.neighborhood}</p>
</div>
<div>
<p className="font-semibold">Cidade</p>
<p>{patientDetails.cidade}</p>
</div>
<div>
<p className="font-semibold">Estado</p>
<p>{patientDetails.estado}</p>
</div>
<div>
<p className="font-semibold">CEP</p>
<p>{patientDetails.cep}</p>
</div>
<div>
<p className="font-semibold">Rua</p>
<p>{`${patientDetails.street}, ${patientDetails.number}`}</p>
</div>
<div>
<p className="font-semibold">Complemento</p>
<p>{patientDetails.complement}</p>
</div>
<div>
<p className="font-semibold">Bairro</p>
<p>{patientDetails.neighborhood}</p>
</div>
<div>
<p className="font-semibold">Cidade</p>
<p>{patientDetails.cidade}</p>
</div>
<div>
<p className="font-semibold">Estado</p>
<p>{patientDetails.estado}</p>
</div>
<div>
<p className="font-semibold">CEP</p>
<p>{patientDetails.cep}</p>
</div>
</div>
</div>
</div>

View File

@ -1,159 +1,67 @@
"use client"
import { useState, useEffect } from "react"
import { useState, useEffect, useMemo } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { toast } from "@/hooks/use-toast"
import { FileText, Download, Eye, Calendar, User, X } from "lucide-react"
import { FileText, Download, Eye, Calendar, User, X, Loader2 } from "lucide-react"
import Sidebar from "@/components/Sidebar"
import { useAuthLayout } from "@/hooks/useAuthLayout"
import { reportsApi } from "@/services/reportsApi.mjs"
interface Report {
id: string
title: string
doctor: string
date: string
type: string
status: "disponivel" | "pendente"
description: string
content: {
patientInfo: {
name: string
age: number
gender: string
id: string
}
examDetails: {
requestingDoctor: string
examDate: string
reportDate: string
technique: string
}
findings: string
conclusion: string
recommendations?: string
}
id: string;
order_number: string;
patient_id: string;
status: string;
exam: string;
requested_by: string;
cid_code: string;
diagnosis: string;
conclusion: string;
content_html: string;
content_json: any;
hide_date: boolean;
hide_signature: boolean;
due_at: string;
created_by: string;
updated_by: string;
created_at: string;
updated_at: string;
}
export default function ReportsPage() {
const [reports, setReports] = useState<Report[]>([])
const [selectedReport, setSelectedReport] = useState<Report | null>(null)
const [isViewModalOpen, setIsViewModalOpen] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const requiredRole = useMemo(() => ["paciente"], []);
const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole });
useEffect(() => {
const mockReports: Report[] = [
{
id: "1",
title: "Exame de Sangue - Hemograma Completo",
doctor: "Dr. João Silva",
date: "2024-01-15",
type: "Exame Laboratorial",
status: "disponivel",
description: "Hemograma completo com contagem de células sanguíneas",
content: {
patientInfo: {
name: "Maria Silva Santos",
age: 35,
gender: "Feminino",
id: "123.456.789-00",
},
examDetails: {
requestingDoctor: "Dr. João Silva - CRM 12345",
examDate: "15/01/2024",
reportDate: "15/01/2024",
technique: "Análise automatizada com confirmação microscópica",
},
findings:
"Hemácias: 4.5 milhões/mm³ (VR: 4.0-5.2)\nHemoglobina: 13.2 g/dL (VR: 12.0-15.5)\nHematócrito: 40% (VR: 36-46)\nLeucócitos: 7.200/mm³ (VR: 4.000-11.000)\nPlaquetas: 280.000/mm³ (VR: 150.000-450.000)\n\nFórmula leucocitária:\n- Neutrófilos: 65% (VR: 50-70%)\n- Linfócitos: 28% (VR: 20-40%)\n- Monócitos: 5% (VR: 2-8%)\n- Eosinófilos: 2% (VR: 1-4%)",
conclusion:
"Hemograma dentro dos parâmetros normais. Não foram observadas alterações significativas na série vermelha, branca ou plaquetária.",
recommendations: "Manter acompanhamento médico regular. Repetir exame conforme orientação médica.",
},
},
{
id: "2",
title: "Radiografia do Tórax",
doctor: "Dra. Maria Santos",
date: "2024-01-10",
type: "Exame de Imagem",
status: "disponivel",
description: "Radiografia PA e perfil do tórax",
content: {
patientInfo: {
name: "Maria Silva Santos",
age: 35,
gender: "Feminino",
id: "123.456.789-00",
},
examDetails: {
requestingDoctor: "Dra. Maria Santos - CRM 67890",
examDate: "10/01/2024",
reportDate: "10/01/2024",
technique: "Radiografia digital PA e perfil",
},
findings:
"Campos pulmonares livres, sem sinais de consolidação ou derrame pleural. Silhueta cardíaca dentro dos limites normais. Estruturas ósseas íntegras. Diafragmas em posição normal.",
conclusion: "Radiografia de tórax sem alterações patológicas evidentes.",
recommendations: "Correlacionar com quadro clínico. Acompanhamento conforme indicação médica.",
},
},
{
id: "3",
title: "Eletrocardiograma",
doctor: "Dr. Carlos Oliveira",
date: "2024-01-08",
type: "Exame Cardiológico",
status: "pendente",
description: "ECG de repouso para avaliação cardíaca",
content: {
patientInfo: {
name: "Maria Silva Santos",
age: 35,
gender: "Feminino",
id: "123.456.789-00",
},
examDetails: {
requestingDoctor: "Dr. Carlos Oliveira - CRM 54321",
examDate: "08/01/2024",
reportDate: "",
technique: "ECG de repouso",
},
findings: "",
conclusion: "",
recommendations: "",
},
},
{
id: "4",
title: "Ultrassom Abdominal",
doctor: "Dra. Ana Costa",
date: "2024-01-05",
type: "Exame de Imagem",
status: "disponivel",
description: "Ultrassonografia do abdome total",
content: {
patientInfo: {
name: "Maria Silva Santos",
age: 35,
gender: "Feminino",
id: "123.456.789-00",
},
examDetails: {
requestingDoctor: "Dra. Ana Costa - CRM 98765",
examDate: "05/01/2024",
reportDate: "05/01/2024",
technique: "Ultrassom convencional",
},
findings:
"Viscerais bem posicionadas. Rim direito e esquerdo com contornos normais. Vesícula com volume dentro do normal.",
conclusion: "Ultrassom abdominal sem alterações patológicas evidentes.",
recommendations: "Acompanhamento conforme indicação médica.",
},
},
]
setReports(mockReports)
}, [])
if (user) {
const fetchReports = async () => {
try {
setIsLoading(true);
const fetchedReports = await reportsApi.getReports(user.id);
setReports(fetchedReports);
} catch (error) {
console.error("Erro ao buscar laudos:", error)
toast({
title: "Erro ao buscar laudos",
description: "Não foi possível carregar os laudos. Tente novamente.",
variant: "destructive",
})
} finally {
setIsLoading(false);
}
}
fetchReports()
}
}, [user?.id])
const handleViewReport = (reportId: string) => {
const report = reports.find((r) => r.id === reportId)
@ -168,100 +76,23 @@ export default function ReportsPage() {
if (!report) return
try {
// Simular loading
toast({
title: "Preparando download...",
description: "Gerando PDF do laudo médico",
})
// Criar conteúdo HTML do laudo para conversão em PDF
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Laudo Médico - ${report.title}</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }
.header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 20px; margin-bottom: 30px; }
.section { margin-bottom: 25px; }
.section-title { font-size: 16px; font-weight: bold; color: #333; margin-bottom: 10px; border-bottom: 1px solid #ccc; padding-bottom: 5px; }
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px; }
.info-item { margin-bottom: 8px; }
.label { font-weight: bold; color: #555; }
.content { white-space: pre-line; }
.footer { margin-top: 40px; text-align: center; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class="header">
<h1>LAUDO MÉDICO</h1>
<h2>${report.title}</h2>
<p><strong>Tipo:</strong> ${report.type}</p>
</div>
const htmlContent = report.content_html;
<div class="section">
<div class="section-title">DADOS DO PACIENTE</div>
<div class="info-grid">
<div class="info-item"><span class="label">Nome:</span> ${report.content.patientInfo.name}</div>
<div class="info-item"><span class="label">Idade:</span> ${report.content.patientInfo.age} anos</div>
<div class="info-item"><span class="label">Sexo:</span> ${report.content.patientInfo.gender}</div>
<div class="info-item"><span class="label">CPF:</span> ${report.content.patientInfo.id}</div>
</div>
</div>
<div class="section">
<div class="section-title">DETALHES DO EXAME</div>
<div class="info-grid">
<div class="info-item"><span class="label">Médico Solicitante:</span> ${report.content.examDetails.requestingDoctor}</div>
<div class="info-item"><span class="label">Data do Exame:</span> ${report.content.examDetails.examDate}</div>
<div class="info-item"><span class="label">Data do Laudo:</span> ${report.content.examDetails.reportDate}</div>
<div class="info-item"><span class="label">Técnica:</span> ${report.content.examDetails.technique}</div>
</div>
</div>
<div class="section">
<div class="section-title">ACHADOS</div>
<div class="content">${report.content.findings}</div>
</div>
<div class="section">
<div class="section-title">CONCLUSÃO</div>
<div class="content">${report.content.conclusion}</div>
</div>
${
report.content.recommendations
? `
<div class="section">
<div class="section-title">RECOMENDAÇÕES</div>
<div class="content">${report.content.recommendations}</div>
</div>
`
: ""
}
<div class="footer">
<p>Documento gerado em ${new Date().toLocaleDateString("pt-BR")} às ${new Date().toLocaleTimeString("pt-BR")}</p>
<p>Este é um documento médico oficial. Mantenha-o em local seguro.</p>
</div>
</body>
</html>
`
// Criar blob com o conteúdo HTML
const blob = new Blob([htmlContent], { type: "text/html" })
const url = URL.createObjectURL(blob)
// Criar link temporário para download
const link = document.createElement("a")
link.href = url
link.download = `laudo-${report.title.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase()}-${report.date}.html`
link.download = `laudo-${report.order_number}.html`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
// Limpar URL temporária
URL.revokeObjectURL(url)
toast({
@ -283,8 +114,18 @@ export default function ReportsPage() {
setSelectedReport(null)
}
const availableReports = reports.filter((report) => report.status === "disponivel")
const pendingReports = reports.filter((report) => report.status === "pendente")
const availableReports = reports.filter((report) => report.status.toLowerCase() === "draft")
const pendingReports = reports.filter((report) => report.status.toLowerCase() !== "draft")
if (isLoading || isAuthLoading) {
return (
<Sidebar>
<div className="flex justify-center items-center h-full">
<Loader2 className="h-8 w-8 animate-spin" />
</div>
</Sidebar>
)
}
return (
<Sidebar>
@ -333,25 +174,25 @@ export default function ReportsPage() {
<CardHeader>
<div className="flex items-start justify-between">
<div className="space-y-1">
<CardTitle className="text-lg">{report.title}</CardTitle>
<CardTitle className="text-lg">{report.exam}</CardTitle>
<CardDescription className="flex items-center gap-4">
<span className="flex items-center gap-1">
<User className="h-4 w-4" />
{report.doctor}
{report.requested_by}
</span>
<span className="flex items-center gap-1">
<Calendar className="h-4 w-4" />
{new Date(report.date).toLocaleDateString("pt-BR")}
{new Date(report.created_at).toLocaleDateString("pt-BR")}
</span>
</CardDescription>
</div>
<Badge variant="secondary" className="bg-green-100 text-green-800">
{report.type}
Finalizado
</Badge>
</div>
</CardHeader>
<CardContent>
<p className="text-gray-600 mb-4">{report.description}</p>
<p className="text-gray-600 mb-4">{report.diagnosis}</p>
<div className="flex gap-2">
<Button
variant="outline"
@ -369,7 +210,7 @@ export default function ReportsPage() {
className="flex items-center gap-2"
>
<Download className="h-4 w-4" />
Baixar PDF
Baixar
</Button>
</div>
</CardContent>
@ -388,25 +229,25 @@ export default function ReportsPage() {
<CardHeader>
<div className="flex items-start justify-between">
<div className="space-y-1">
<CardTitle className="text-lg">{report.title}</CardTitle>
<CardTitle className="text-lg">{report.exam}</CardTitle>
<CardDescription className="flex items-center gap-4">
<span className="flex items-center gap-1">
<User className="h-4 w-4" />
{report.doctor}
{report.requested_by}
</span>
<span className="flex items-center gap-1">
<Calendar className="h-4 w-4" />
{new Date(report.date).toLocaleDateString("pt-BR")}
{new Date(report.created_at).toLocaleDateString("pt-BR")}
</span>
</CardDescription>
</div>
<Badge variant="secondary" className="bg-yellow-100 text-yellow-800">
Pendente
{report.status}
</Badge>
</div>
</CardHeader>
<CardContent>
<p className="text-gray-600 mb-4">{report.description}</p>
<p className="text-gray-600 mb-4">{report.diagnosis}</p>
<p className="text-sm text-yellow-600 font-medium">
Laudo em processamento. Você será notificado quando estiver disponível.
</p>
@ -417,7 +258,7 @@ export default function ReportsPage() {
</div>
)}
{reports.length === 0 && (
{reports.length === 0 && !isLoading && (
<Card className="text-center py-12">
<CardContent>
<FileText className="h-12 w-12 text-gray-400 mx-auto mb-4" />
@ -432,9 +273,9 @@ export default function ReportsPage() {
<DialogHeader>
<div className="flex items-center justify-between">
<div>
<DialogTitle className="text-xl font-bold">{selectedReport?.title}</DialogTitle>
<DialogTitle className="text-xl font-bold">{selectedReport?.exam}</DialogTitle>
<DialogDescription className="mt-1">
{selectedReport?.type} - {selectedReport?.doctor}
{selectedReport?.order_number}
</DialogDescription>
</div>
<Button variant="ghost" size="sm" onClick={handleCloseModal} className="h-8 w-8 p-0">
@ -444,94 +285,7 @@ export default function ReportsPage() {
</DialogHeader>
{selectedReport && (
<div className="space-y-6 mt-4">
<Card>
<CardHeader>
<CardTitle className="text-lg">Dados do Paciente</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm font-medium text-gray-500">Nome</p>
<p className="text-sm">{selectedReport.content.patientInfo.name}</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500">Idade</p>
<p className="text-sm">{selectedReport.content.patientInfo.age} anos</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500">Sexo</p>
<p className="text-sm">{selectedReport.content.patientInfo.gender}</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500">CPF</p>
<p className="text-sm">{selectedReport.content.patientInfo.id}</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-lg">Detalhes do Exame</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm font-medium text-gray-500">Médico Solicitante</p>
<p className="text-sm">{selectedReport.content.examDetails.requestingDoctor}</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500">Data do Exame</p>
<p className="text-sm">{selectedReport.content.examDetails.examDate}</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500">Data do Laudo</p>
<p className="text-sm">{selectedReport.content.examDetails.reportDate}</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500">Técnica</p>
<p className="text-sm">{selectedReport.content.examDetails.technique}</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-lg">Achados</CardTitle>
</CardHeader>
<CardContent>
<div className="whitespace-pre-line text-sm leading-relaxed">{selectedReport.content.findings}</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-lg">Conclusão</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm leading-relaxed">{selectedReport.content.conclusion}</p>
</CardContent>
</Card>
{selectedReport.content.recommendations && (
<Card>
<CardHeader>
<CardTitle className="text-lg">Recomendações</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm leading-relaxed">{selectedReport.content.recommendations}</p>
</CardContent>
</Card>
)}
<div className="flex gap-3 pt-4 border-t">
<Button onClick={() => handleDownloadReport(selectedReport.id)} className="flex items-center gap-2">
<Download className="h-4 w-4" />
Baixar PDF
</Button>
<Button variant="outline" onClick={handleCloseModal}>
Fechar
</Button>
</div>
</div>
<div className="space-y-6 mt-4" dangerouslySetInnerHTML={{ __html: selectedReport.content_html }} />
)}
</DialogContent>
</Dialog>

View File

@ -6,7 +6,7 @@ import Link from "next/link";
import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react";
import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2, MoreVertical } from "lucide-react";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import { patientsService } from "@/services/patientsApi.mjs";
import Sidebar from "@/components/Sidebar";
@ -164,7 +164,7 @@ export default function PacientesPage() {
</div>
{/* Bloco de Filtros (Responsividade APLICADA) */}
<div className="flex flex-wrap items-center gap-4 bg-card p-4 rounded-lg border border-border">
<div className="flex flex-wrap items-center gap-4 bg-card p-4 rounded-lg border border-border">
<Filter className="w-5 h-5 text-gray-400" />
{/* Busca - Ocupa 100% no mobile, depois cresce */}
@ -272,7 +272,10 @@ export default function PacientesPage() {
<td className="p-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="text-blue-600 cursor-pointer">Ações</div>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Abrir menu</span>
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>
@ -329,38 +332,38 @@ export default function PacientesPage() {
{patient.nome}
{patient.vip && (
<span className="ml-2 px-2 py-0.5 text-xs font-semibold text-purple-600 bg-purple-100 rounded-full">VIP</span>
)}
)}
</div>
<div className="text-sm text-gray-600">Telefone: {patient.telefone}</div>
<div className="text-sm text-gray-600">Convênio: {patient.convenio}</div>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="w-full"><Button variant="outline" className="w-full">Ações</Button></div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>
<Eye className="w-4 h-4 mr-2" />
Ver detalhes
</DropdownMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="w-full"><Button variant="outline" className="w-full">Ações</Button></div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>
<Eye className="w-4 h-4 mr-2" />
Ver detalhes
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/secretary/pacientes/${patient.id}/editar`} className="flex items-center w-full">
<Edit className="w-4 h-4 mr-2" />
Editar
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/secretary/pacientes/${patient.id}/editar`} className="flex items-center w-full">
<Edit className="w-4 h-4 mr-2" />
Editar
</Link>
</DropdownMenuItem>
<DropdownMenuItem>
<Calendar className="w-4 h-4 mr-2" />
Marcar consulta
</DropdownMenuItem>
<DropdownMenuItem className="text-red-600" onClick={() => openDeleteDialog(String(patient.id))}>
<Trash2 className="w-4 h-4 mr-2" />
Excluir
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenuItem>
<Calendar className="w-4 h-4 mr-2" />
Marcar consulta
</DropdownMenuItem>
<DropdownMenuItem className="text-red-600" onClick={() => openDeleteDialog(String(patient.id))}>
<Trash2 className="w-4 h-4 mr-2" />
Excluir
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
))}
</div>
@ -438,65 +441,65 @@ export default function PacientesPage() {
<div className="grid gap-4 py-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<p className="font-semibold">Nome Completo</p>
<p>{patientDetails.full_name}</p>
<p className="font-semibold">Nome Completo</p>
<p>{patientDetails.full_name}</p>
</div>
<div>
<p className="font-semibold">Email</p>
<p>{patientDetails.email}</p>
<p className="font-semibold">Email</p>
<p>{patientDetails.email}</p>
</div>
<div>
<p className="font-semibold">Telefone</p>
<p>{patientDetails.phone_mobile}</p>
<p className="font-semibold">Telefone</p>
<p>{patientDetails.phone_mobile}</p>
</div>
<div>
<p className="font-semibold">Data de Nascimento</p>
<p>{patientDetails.birth_date}</p>
<p className="font-semibold">Data de Nascimento</p>
<p>{patientDetails.birth_date}</p>
</div>
<div>
<p className="font-semibold">CPF</p>
<p>{patientDetails.cpf}</p>
<p className="font-semibold">CPF</p>
<p>{patientDetails.cpf}</p>
</div>
<div>
<p className="font-semibold">Tipo Sanguíneo</p>
<p>{patientDetails.blood_type}</p>
<p className="font-semibold">Tipo Sanguíneo</p>
<p>{patientDetails.blood_type}</p>
</div>
<div>
<p className="font-semibold">Peso (kg)</p>
<p>{patientDetails.weight_kg}</p>
<p className="font-semibold">Peso (kg)</p>
<p>{patientDetails.weight_kg}</p>
</div>
<div>
<p className="font-semibold">Altura (m)</p>
<p>{patientDetails.height_m}</p>
<p className="font-semibold">Altura (m)</p>
<p>{patientDetails.height_m}</p>
</div>
</div>
<div className="border-t pt-4 mt-4">
<h3 className="font-semibold mb-2">Endereço</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<p className="font-semibold">Rua</p>
<p>{`${patientDetails.street}, ${patientDetails.number}`}</p>
</div>
<div>
<p className="font-semibold">Complemento</p>
<p>{patientDetails.complement}</p>
</div>
<div>
<p className="font-semibold">Bairro</p>
<p>{patientDetails.neighborhood}</p>
</div>
<div>
<p className="font-semibold">Cidade</p>
<p>{patientDetails.cidade}</p>
</div>
<div>
<p className="font-semibold">Estado</p>
<p>{patientDetails.estado}</p>
</div>
<div>
<p className="font-semibold">CEP</p>
<p>{patientDetails.cep}</p>
</div>
<div>
<p className="font-semibold">Rua</p>
<p>{`${patientDetails.street}, ${patientDetails.number}`}</p>
</div>
<div>
<p className="font-semibold">Complemento</p>
<p>{patientDetails.complement}</p>
</div>
<div>
<p className="font-semibold">Bairro</p>
<p>{patientDetails.neighborhood}</p>
</div>
<div>
<p className="font-semibold">Cidade</p>
<p>{patientDetails.cidade}</p>
</div>
<div>
<p className="font-semibold">Estado</p>
<p>{patientDetails.estado}</p>
</div>
<div>
<p className="font-semibold">CEP</p>
<p>{patientDetails.cep}</p>
</div>
</div>
</div>
</div>

View File

@ -9,7 +9,7 @@ export const reportsApi = {
return data;
} catch (error) {
console.error("Failed to fetch reports:", error);
return [];
throw error;
}
},
getReportById: async (reportId) => {