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> <CardContent>
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-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"> <div className="space-y-2">
<Label htmlFor="exam">Exame</Label> <Label htmlFor="exam">Exame</Label>
<Input id="exam" value={formData.exam} onChange={handleInputChange} /> <Input id="exam" value={formData.exam} onChange={handleInputChange} />

View File

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

View File

@ -6,7 +6,7 @@ import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" 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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"
import { doctorsService } from "services/doctorsApi.mjs"; import { doctorsService } from "services/doctorsApi.mjs";
@ -284,7 +284,10 @@ export default function DoctorsPage() {
<td className="px-4 py-3 text-right"> <td className="px-4 py-3 text-right">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <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> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(doctor)}> <DropdownMenuItem onClick={() => openDetailsDialog(doctor)}>
@ -342,7 +345,7 @@ export default function DoctorsPage() {
</div> </div>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <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> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(doctor)}> <DropdownMenuItem onClick={() => openDetailsDialog(doctor)}>
@ -386,8 +389,7 @@ export default function DoctorsPage() {
<button <button
key={number} key={number}
onClick={() => paginate(number)} onClick={() => paginate(number)}
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm border border-gray-300 ${ className={`px-4 py-2 rounded-md font-medium transition-colors text-sm border border-gray-300 ${currentPage === number
currentPage === number
? "bg-green-600 text-white shadow-md border-green-600" ? "bg-green-600 text-white shadow-md border-green-600"
: "bg-gray-100 text-gray-700 hover:bg-gray-200" : "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`} }`}

View File

@ -5,7 +5,7 @@ import Link from "next/link";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; 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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import { patientsService } from "@/services/patientsApi.mjs"; import { patientsService } from "@/services/patientsApi.mjs";
import Sidebar from "@/components/Sidebar"; import Sidebar from "@/components/Sidebar";
@ -264,7 +264,10 @@ export default function PacientesPage() {
<td className="p-4"> <td className="p-4">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <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> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}> <DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>
@ -328,7 +331,10 @@ export default function PacientesPage() {
</div> </div>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<div className="w-full"><Button variant="outline" className="w-full">Ações</Button></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> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}> <DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>

View File

@ -1,159 +1,67 @@
"use client" "use client"
import { useState, useEffect } from "react" import { useState, useEffect, useMemo } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { toast } from "@/hooks/use-toast" 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 Sidebar from "@/components/Sidebar"
import { useAuthLayout } from "@/hooks/useAuthLayout"
import { reportsApi } from "@/services/reportsApi.mjs"
interface Report { interface Report {
id: string id: string;
title: string order_number: string;
doctor: string patient_id: string;
date: string status: string;
type: string exam: string;
status: "disponivel" | "pendente" requested_by: string;
description: string cid_code: string;
content: { diagnosis: string;
patientInfo: { conclusion: string;
name: string content_html: string;
age: number content_json: any;
gender: string hide_date: boolean;
id: string hide_signature: boolean;
} due_at: string;
examDetails: { created_by: string;
requestingDoctor: string updated_by: string;
examDate: string created_at: string;
reportDate: string updated_at: string;
technique: string
}
findings: string
conclusion: string
recommendations?: string
}
} }
export default function ReportsPage() { export default function ReportsPage() {
const [reports, setReports] = useState<Report[]>([]) const [reports, setReports] = useState<Report[]>([])
const [selectedReport, setSelectedReport] = useState<Report | null>(null) const [selectedReport, setSelectedReport] = useState<Report | null>(null)
const [isViewModalOpen, setIsViewModalOpen] = useState(false) const [isViewModalOpen, setIsViewModalOpen] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const requiredRole = useMemo(() => ["paciente"], []);
const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole });
useEffect(() => { useEffect(() => {
const mockReports: Report[] = [ if (user) {
{ const fetchReports = async () => {
id: "1", try {
title: "Exame de Sangue - Hemograma Completo", setIsLoading(true);
doctor: "Dr. João Silva", const fetchedReports = await reportsApi.getReports(user.id);
date: "2024-01-15", setReports(fetchedReports);
type: "Exame Laboratorial", } catch (error) {
status: "disponivel", console.error("Erro ao buscar laudos:", error)
description: "Hemograma completo com contagem de células sanguíneas", toast({
content: { title: "Erro ao buscar laudos",
patientInfo: { description: "Não foi possível carregar os laudos. Tente novamente.",
name: "Maria Silva Santos", variant: "destructive",
age: 35, })
gender: "Feminino", } finally {
id: "123.456.789-00", setIsLoading(false);
}, }
examDetails: { }
requestingDoctor: "Dr. João Silva - CRM 12345", fetchReports()
examDate: "15/01/2024", }
reportDate: "15/01/2024", }, [user?.id])
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)
}, [])
const handleViewReport = (reportId: string) => { const handleViewReport = (reportId: string) => {
const report = reports.find((r) => r.id === reportId) const report = reports.find((r) => r.id === reportId)
@ -168,100 +76,23 @@ export default function ReportsPage() {
if (!report) return if (!report) return
try { try {
// Simular loading
toast({ toast({
title: "Preparando download...", title: "Preparando download...",
description: "Gerando PDF do laudo médico", description: "Gerando PDF do laudo médico",
}) })
// Criar conteúdo HTML do laudo para conversão em PDF const htmlContent = report.content_html;
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>
<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 blob = new Blob([htmlContent], { type: "text/html" })
const url = URL.createObjectURL(blob) const url = URL.createObjectURL(blob)
// Criar link temporário para download
const link = document.createElement("a") const link = document.createElement("a")
link.href = url 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) document.body.appendChild(link)
link.click() link.click()
document.body.removeChild(link) document.body.removeChild(link)
// Limpar URL temporária
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
toast({ toast({
@ -283,8 +114,18 @@ export default function ReportsPage() {
setSelectedReport(null) setSelectedReport(null)
} }
const availableReports = reports.filter((report) => report.status === "disponivel") const availableReports = reports.filter((report) => report.status.toLowerCase() === "draft")
const pendingReports = reports.filter((report) => report.status === "pendente") 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 ( return (
<Sidebar> <Sidebar>
@ -333,25 +174,25 @@ export default function ReportsPage() {
<CardHeader> <CardHeader>
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="space-y-1"> <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"> <CardDescription className="flex items-center gap-4">
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<User className="h-4 w-4" /> <User className="h-4 w-4" />
{report.doctor} {report.requested_by}
</span> </span>
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<Calendar className="h-4 w-4" /> <Calendar className="h-4 w-4" />
{new Date(report.date).toLocaleDateString("pt-BR")} {new Date(report.created_at).toLocaleDateString("pt-BR")}
</span> </span>
</CardDescription> </CardDescription>
</div> </div>
<Badge variant="secondary" className="bg-green-100 text-green-800"> <Badge variant="secondary" className="bg-green-100 text-green-800">
{report.type} Finalizado
</Badge> </Badge>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <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"> <div className="flex gap-2">
<Button <Button
variant="outline" variant="outline"
@ -369,7 +210,7 @@ export default function ReportsPage() {
className="flex items-center gap-2" className="flex items-center gap-2"
> >
<Download className="h-4 w-4" /> <Download className="h-4 w-4" />
Baixar PDF Baixar
</Button> </Button>
</div> </div>
</CardContent> </CardContent>
@ -388,25 +229,25 @@ export default function ReportsPage() {
<CardHeader> <CardHeader>
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="space-y-1"> <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"> <CardDescription className="flex items-center gap-4">
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<User className="h-4 w-4" /> <User className="h-4 w-4" />
{report.doctor} {report.requested_by}
</span> </span>
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<Calendar className="h-4 w-4" /> <Calendar className="h-4 w-4" />
{new Date(report.date).toLocaleDateString("pt-BR")} {new Date(report.created_at).toLocaleDateString("pt-BR")}
</span> </span>
</CardDescription> </CardDescription>
</div> </div>
<Badge variant="secondary" className="bg-yellow-100 text-yellow-800"> <Badge variant="secondary" className="bg-yellow-100 text-yellow-800">
Pendente {report.status}
</Badge> </Badge>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <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"> <p className="text-sm text-yellow-600 font-medium">
Laudo em processamento. Você será notificado quando estiver disponível. Laudo em processamento. Você será notificado quando estiver disponível.
</p> </p>
@ -417,7 +258,7 @@ export default function ReportsPage() {
</div> </div>
)} )}
{reports.length === 0 && ( {reports.length === 0 && !isLoading && (
<Card className="text-center py-12"> <Card className="text-center py-12">
<CardContent> <CardContent>
<FileText className="h-12 w-12 text-gray-400 mx-auto mb-4" /> <FileText className="h-12 w-12 text-gray-400 mx-auto mb-4" />
@ -432,9 +273,9 @@ export default function ReportsPage() {
<DialogHeader> <DialogHeader>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<DialogTitle className="text-xl font-bold">{selectedReport?.title}</DialogTitle> <DialogTitle className="text-xl font-bold">{selectedReport?.exam}</DialogTitle>
<DialogDescription className="mt-1"> <DialogDescription className="mt-1">
{selectedReport?.type} - {selectedReport?.doctor} {selectedReport?.order_number}
</DialogDescription> </DialogDescription>
</div> </div>
<Button variant="ghost" size="sm" onClick={handleCloseModal} className="h-8 w-8 p-0"> <Button variant="ghost" size="sm" onClick={handleCloseModal} className="h-8 w-8 p-0">
@ -444,94 +285,7 @@ export default function ReportsPage() {
</DialogHeader> </DialogHeader>
{selectedReport && ( {selectedReport && (
<div className="space-y-6 mt-4"> <div className="space-y-6 mt-4" dangerouslySetInnerHTML={{ __html: selectedReport.content_html }} />
<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>
)} )}
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -6,7 +6,7 @@ import Link from "next/link";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; 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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import { patientsService } from "@/services/patientsApi.mjs"; import { patientsService } from "@/services/patientsApi.mjs";
import Sidebar from "@/components/Sidebar"; import Sidebar from "@/components/Sidebar";
@ -272,7 +272,10 @@ export default function PacientesPage() {
<td className="p-4"> <td className="p-4">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <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> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}> <DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>

View File

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