388 lines
15 KiB
TypeScript
388 lines
15 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { useRouter, useParams } from 'next/navigation'
|
|
import { useTheme } from 'next-themes'
|
|
import Image from 'next/image'
|
|
import { Button } from '@/components/ui/button'
|
|
import { ArrowLeft, Printer, Download, MoreVertical } from 'lucide-react'
|
|
import { buscarRelatorioPorId, getDoctorById, buscarMedicosPorIds } from '@/lib/api'
|
|
import { ENV_CONFIG } from '@/lib/env-config'
|
|
import ProtectedRoute from '@/components/shared/ProtectedRoute'
|
|
import { useAuth } from '@/hooks/useAuth'
|
|
|
|
export default function LaudoPage() {
|
|
const router = useRouter()
|
|
const params = useParams()
|
|
const { user } = useAuth()
|
|
const { theme } = useTheme()
|
|
const reportId = params.id as string
|
|
const isDark = theme === 'dark'
|
|
|
|
const [report, setReport] = useState<any | null>(null)
|
|
const [doctor, setDoctor] = useState<any | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState('')
|
|
|
|
useEffect(() => {
|
|
if (!reportId) return
|
|
|
|
let mounted = true
|
|
|
|
async function loadReport() {
|
|
try {
|
|
setLoading(true)
|
|
const reportData = await buscarRelatorioPorId(reportId)
|
|
|
|
if (!mounted) return
|
|
setReport(reportData)
|
|
|
|
// Load doctor info using the same strategy as paciente/page.tsx
|
|
const rd = reportData as any
|
|
const maybeId = rd?.doctor_id ?? rd?.created_by ?? rd?.doctor ?? null
|
|
|
|
if (maybeId) {
|
|
try {
|
|
// First try: buscarMedicosPorIds
|
|
let doctors = await buscarMedicosPorIds([maybeId]).catch(() => [])
|
|
|
|
if (!doctors || doctors.length === 0) {
|
|
// Second try: getDoctorById
|
|
const doc = await getDoctorById(String(maybeId)).catch(() => null)
|
|
if (doc) doctors = [doc]
|
|
}
|
|
|
|
if (!doctors || doctors.length === 0) {
|
|
// Third try: direct REST with user_id filter
|
|
const token = (typeof window !== 'undefined')
|
|
? (localStorage.getItem('auth_token') || localStorage.getItem('token') ||
|
|
sessionStorage.getItem('auth_token') || sessionStorage.getItem('token'))
|
|
: null
|
|
const headers: Record<string,string> = {
|
|
apikey: (ENV_CONFIG as any).SUPABASE_ANON_KEY,
|
|
Accept: 'application/json'
|
|
}
|
|
if (token) headers.Authorization = `Bearer ${token}`
|
|
const url = `${(ENV_CONFIG as any).SUPABASE_URL}/rest/v1/doctors?user_id=eq.${encodeURIComponent(String(maybeId))}&limit=1`
|
|
const res = await fetch(url, { method: 'GET', headers })
|
|
if (res && res.status < 400) {
|
|
const rows = await res.json().catch(() => [])
|
|
if (rows && Array.isArray(rows) && rows.length) {
|
|
doctors = rows
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mounted && doctors && doctors.length > 0) {
|
|
setDoctor(doctors[0])
|
|
}
|
|
} catch (e) {
|
|
console.warn('Erro ao carregar dados do profissional:', e)
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (mounted) setError('Erro ao carregar o laudo.')
|
|
console.error(err)
|
|
} finally {
|
|
if (mounted) setLoading(false)
|
|
}
|
|
}
|
|
|
|
loadReport()
|
|
return () => { mounted = false }
|
|
}, [reportId])
|
|
|
|
const handlePrint = () => {
|
|
window.print()
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<ProtectedRoute>
|
|
<div className="flex items-center justify-center min-h-screen bg-background">
|
|
<div className="text-lg text-muted-foreground">Carregando laudo...</div>
|
|
</div>
|
|
</ProtectedRoute>
|
|
)
|
|
}
|
|
|
|
if (error || !report) {
|
|
return (
|
|
<ProtectedRoute>
|
|
<div className="flex flex-col items-center justify-center min-h-screen bg-background">
|
|
<div className="text-lg text-red-500 mb-4">{error || 'Laudo não encontrado.'}</div>
|
|
<Button onClick={() => router.back()} variant="outline">
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
Voltar
|
|
</Button>
|
|
</div>
|
|
</ProtectedRoute>
|
|
)
|
|
}
|
|
|
|
// Extract fields with fallbacks
|
|
const reportDate = new Date(report.report_date || report.created_at || Date.now()).toLocaleDateString('pt-BR')
|
|
const cid = report.cid ?? report.cid_code ?? report.cidCode ?? report.cie ?? ''
|
|
const exam = report.exam ?? report.exame ?? report.especialidade ?? report.report_type ?? ''
|
|
const diagnosis = report.diagnosis ?? report.diagnostico ?? report.diagnosis_text ?? report.diagnostico_text ?? ''
|
|
const conclusion = report.conclusion ?? report.conclusao ?? report.conclusion_text ?? report.conclusao_text ?? ''
|
|
const notesHtml = report.content_html ?? report.conteudo_html ?? report.contentHtml ?? null
|
|
const notesText = report.content ?? report.body ?? report.conteudo ?? report.notes ?? report.observacoes ?? ''
|
|
|
|
// Extract doctor name with multiple fallbacks
|
|
let doctorName = ''
|
|
if (doctor) {
|
|
doctorName = doctor.full_name || doctor.name || doctor.fullName || doctor.doctor_name || ''
|
|
}
|
|
if (!doctorName) {
|
|
const rd = report as any
|
|
const tryKeys = [
|
|
'doctor_name', 'doctor_full_name', 'doctorFullName', 'doctorName',
|
|
'requested_by_name', 'requested_by', 'requester_name', 'requester',
|
|
'created_by_name', 'created_by', 'executante', 'executante_name',
|
|
]
|
|
for (const k of tryKeys) {
|
|
const v = rd[k]
|
|
if (v !== undefined && v !== null && String(v).trim() !== '') {
|
|
doctorName = String(v)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<ProtectedRoute>
|
|
<div className={`min-h-screen transition-colors duration-300 ${
|
|
isDark
|
|
? 'bg-gradient-to-br from-slate-950 to-slate-900'
|
|
: 'bg-gradient-to-br from-slate-50 to-slate-100'
|
|
}`}>
|
|
{/* Header Toolbar */}
|
|
<div className={`sticky top-0 z-40 transition-colors duration-300 ${
|
|
isDark
|
|
? 'bg-slate-800 border-slate-700'
|
|
: 'bg-white border-slate-200'
|
|
} border-b shadow-md`}>
|
|
<div className="flex items-center justify-between px-6 py-4">
|
|
{/* Left Section */}
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => router.back()}
|
|
className={`${
|
|
isDark
|
|
? 'text-slate-300 hover:bg-slate-700 hover:text-white'
|
|
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<ArrowLeft className="w-5 h-5" />
|
|
</Button>
|
|
<div className={`h-8 w-px ${isDark ? 'bg-slate-600' : 'bg-slate-300'}`} />
|
|
<div>
|
|
<p className={`text-xs font-semibold uppercase tracking-wide ${
|
|
isDark ? 'text-slate-400' : 'text-slate-500'
|
|
}`}>Laudo Médico</p>
|
|
<p className={`text-lg font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
{doctorName || 'Profissional'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Section */}
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={handlePrint}
|
|
title="Imprimir"
|
|
className={`${
|
|
isDark
|
|
? 'text-slate-300 hover:bg-slate-700 hover:text-white'
|
|
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<Printer className="w-5 h-5" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
title="Mais opções"
|
|
className={`${
|
|
isDark
|
|
? 'text-slate-300 hover:bg-slate-700 hover:text-white'
|
|
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<MoreVertical className="w-5 h-5" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content Area */}
|
|
<div className="flex justify-center py-12 px-4 min-h-[calc(100vh-80px)]">
|
|
{/* Document Container */}
|
|
<div className={`w-full max-w-4xl transition-colors duration-300 shadow-2xl rounded-xl overflow-hidden ${
|
|
isDark ? 'bg-slate-800' : 'bg-white'
|
|
}`}>
|
|
{/* Document Content */}
|
|
<div className="p-16 space-y-8 print:p-0 print:shadow-none">
|
|
|
|
{/* Title */}
|
|
<div className={`text-center mb-12 pb-8 border-b-2 ${
|
|
isDark ? 'border-blue-900' : 'border-blue-200'
|
|
}`}>
|
|
<h1 className={`text-4xl font-bold mb-4 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
RELATÓRIO MÉDICO
|
|
</h1>
|
|
<div className={`text-sm space-y-1 ${isDark ? 'text-slate-300' : 'text-slate-700'}`}>
|
|
<p className="font-medium">
|
|
<span className={isDark ? 'text-slate-400' : 'text-slate-500'}>Data:</span> {reportDate}
|
|
</p>
|
|
{doctorName && (
|
|
<p className="font-medium">
|
|
<span className={isDark ? 'text-slate-400' : 'text-slate-500'}>Profissional:</span>{' '}
|
|
<strong className={isDark ? 'text-blue-400' : 'text-blue-700'}>{doctorName}</strong>
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Patient/Header Info */}
|
|
<div className={`rounded-lg p-6 border transition-colors duration-300 ${
|
|
isDark
|
|
? 'bg-slate-900 border-slate-700'
|
|
: 'bg-slate-50 border-slate-200'
|
|
}`}>
|
|
<div className="grid grid-cols-2 gap-6 text-sm">
|
|
{cid && (
|
|
<div>
|
|
<label className={`text-xs uppercase font-semibold tracking-wide block mb-2 ${
|
|
isDark ? 'text-slate-400' : 'text-slate-600'
|
|
}`}>CID</label>
|
|
<p className={`text-lg font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
{cid}
|
|
</p>
|
|
</div>
|
|
)}
|
|
{exam && (
|
|
<div>
|
|
<label className={`text-xs uppercase font-semibold tracking-wide block mb-2 ${
|
|
isDark ? 'text-slate-400' : 'text-slate-600'
|
|
}`}>Exame / Tipo</label>
|
|
<p className={`text-lg font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
{exam}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Diagnosis Section */}
|
|
{diagnosis && (
|
|
<div className="space-y-3">
|
|
<h2 className={`text-xl font-bold uppercase tracking-wide ${
|
|
isDark ? 'text-blue-400' : 'text-blue-700'
|
|
}`}>Diagnóstico</h2>
|
|
<div className={`whitespace-pre-wrap text-base leading-relaxed rounded-lg p-4 border-l-4 border-blue-500 transition-colors duration-300 ${
|
|
isDark
|
|
? 'bg-slate-900 text-slate-200'
|
|
: 'bg-blue-50 text-slate-800'
|
|
}`}>
|
|
{diagnosis}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Conclusion Section */}
|
|
{conclusion && (
|
|
<div className="space-y-3">
|
|
<h2 className={`text-xl font-bold uppercase tracking-wide ${
|
|
isDark ? 'text-blue-400' : 'text-blue-700'
|
|
}`}>Conclusão</h2>
|
|
<div className={`whitespace-pre-wrap text-base leading-relaxed rounded-lg p-4 border-l-4 border-blue-500 transition-colors duration-300 ${
|
|
isDark
|
|
? 'bg-slate-900 text-slate-200'
|
|
: 'bg-blue-50 text-slate-800'
|
|
}`}>
|
|
{conclusion}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Notes/Content Section */}
|
|
{(notesHtml || notesText) && (
|
|
<div className="space-y-3">
|
|
<h2 className={`text-xl font-bold uppercase tracking-wide ${
|
|
isDark ? 'text-blue-400' : 'text-blue-700'
|
|
}`}>Notas do Profissional</h2>
|
|
{notesHtml ? (
|
|
<div
|
|
className={`prose prose-sm max-w-none rounded-lg p-4 border-l-4 border-blue-500 transition-colors duration-300 ${
|
|
isDark
|
|
? 'prose-invert bg-slate-900 text-slate-200'
|
|
: 'bg-blue-50 text-slate-800'
|
|
}`}
|
|
dangerouslySetInnerHTML={{ __html: String(notesHtml) }}
|
|
/>
|
|
) : (
|
|
<div className={`whitespace-pre-wrap text-base leading-relaxed rounded-lg p-4 border-l-4 border-blue-500 transition-colors duration-300 ${
|
|
isDark
|
|
? 'bg-slate-900 text-slate-200'
|
|
: 'bg-blue-50 text-slate-800'
|
|
}`}>
|
|
{notesText}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Signature Section */}
|
|
{report.doctor_signature && (
|
|
<div className={`pt-8 border-t-2 ${isDark ? 'border-slate-600' : 'border-slate-300'}`}>
|
|
<div className="flex flex-col items-center gap-4">
|
|
<div className={`rounded-lg p-4 border transition-colors duration-300 ${
|
|
isDark
|
|
? 'bg-slate-900 border-slate-600'
|
|
: 'bg-slate-100 border-slate-300'
|
|
}`}>
|
|
<Image
|
|
src={report.doctor_signature}
|
|
alt="Assinatura do profissional"
|
|
width={150}
|
|
height={100}
|
|
className="h-20 w-auto"
|
|
/>
|
|
</div>
|
|
{doctorName && (
|
|
<div className="text-center">
|
|
<p className={`text-sm font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
{doctorName}
|
|
</p>
|
|
{doctor?.crm && (
|
|
<p className={`text-xs mt-1 ${isDark ? 'text-slate-400' : 'text-slate-600'}`}>
|
|
CRM: {doctor.crm}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Footer */}
|
|
<div className={`pt-8 border-t-2 text-center space-y-2 ${isDark ? 'border-slate-600' : 'border-slate-300'}`}>
|
|
<p className={`text-xs ${isDark ? 'text-slate-400' : 'text-slate-600'}`}>
|
|
Documento gerado em {new Date().toLocaleString('pt-BR')}
|
|
</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ProtectedRoute>
|
|
)
|
|
}
|