fix-report-page
This commit is contained in:
parent
e22ad305c4
commit
4d02b55ce7
@ -96,6 +96,136 @@ export default function LaudoPage() {
|
||||
window.print()
|
||||
}
|
||||
|
||||
const handleDownloadPDF = async () => {
|
||||
if (!report) return
|
||||
|
||||
try {
|
||||
// Para simplificar, vamos usar jsPDF com html2canvas para capturar o conteúdo
|
||||
const { jsPDF } = await import('jspdf')
|
||||
const html2canvas = await import('html2canvas').then((m) => m.default)
|
||||
|
||||
// Criar um elemento temporário com o conteúdo
|
||||
const element = document.createElement('div')
|
||||
element.style.position = 'absolute'
|
||||
element.style.left = '-9999px'
|
||||
element.style.width = '210mm' // A4 width
|
||||
element.style.padding = '20mm'
|
||||
element.style.backgroundColor = 'white'
|
||||
element.style.fontFamily = 'Arial, sans-serif'
|
||||
|
||||
// Extrair informações
|
||||
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 notesText = report.content ?? report.body ?? report.conteudo ?? report.notes ?? report.observacoes ?? ''
|
||||
|
||||
// Extrair nome do médico
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Montar HTML do documento
|
||||
element.innerHTML = `
|
||||
<div style="border-bottom: 2px solid #3b82f6; padding-bottom: 10px; margin-bottom: 20px;">
|
||||
<h1 style="text-align: center; font-size: 24px; font-weight: bold; color: #1f2937; margin: 0;">RELATÓRIO MÉDICO</h1>
|
||||
<p style="text-align: center; font-size: 10px; color: #6b7280; margin: 5px 0;">Data: ${reportDate}</p>
|
||||
${doctorName ? `<p style="text-align: center; font-size: 10px; color: #6b7280; margin: 5px 0;">Profissional: ${doctorName}</p>` : ''}
|
||||
</div>
|
||||
|
||||
<div style="background-color: #f0f9ff; border: 1px solid #bfdbfe; padding: 10px; margin-bottom: 15px;">
|
||||
<div style="display: flex; gap: 20px;">
|
||||
${cid ? `<div><p style="font-size: 9px; font-weight: bold; color: #475569; margin: 0 0 5px 0;">CID</p><p style="font-size: 11px; font-weight: bold; color: #1f2937; margin: 0;">${cid}</p></div>` : ''}
|
||||
${exam ? `<div><p style="font-size: 9px; font-weight: bold; color: #475569; margin: 0 0 5px 0;">EXAME / TIPO</p><p style="font-size: 11px; font-weight: bold; color: #1f2937; margin: 0;">${exam}</p></div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${diagnosis ? `
|
||||
<div style="margin-bottom: 20px;">
|
||||
<h2 style="font-size: 14px; font-weight: bold; color: #1e40af; margin: 0 0 10px 0;">DIAGNÓSTICO</h2>
|
||||
<p style="margin-left: 10px; padding-left: 10px; border-left: 2px solid #3b82f6; background-color: #f3f4f6; font-size: 10px; line-height: 1.5; margin: 0;">${diagnosis}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${conclusion ? `
|
||||
<div style="margin-bottom: 20px;">
|
||||
<h2 style="font-size: 14px; font-weight: bold; color: #1e40af; margin: 0 0 10px 0;">CONCLUSÃO</h2>
|
||||
<p style="margin-left: 10px; padding-left: 10px; border-left: 2px solid #3b82f6; background-color: #f3f4f6; font-size: 10px; line-height: 1.5; margin: 0;">${conclusion}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${notesText ? `
|
||||
<div style="margin-bottom: 20px;">
|
||||
<h2 style="font-size: 14px; font-weight: bold; color: #1e40af; margin: 0 0 10px 0;">NOTAS DO PROFISSIONAL</h2>
|
||||
<p style="margin-left: 10px; padding-left: 10px; border-left: 2px solid #3b82f6; background-color: #f3f4f6; font-size: 10px; line-height: 1.5; margin: 0;">${notesText}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div style="margin-top: 30px; padding-top: 10px; border-top: 1px solid #e5e7eb; font-size: 8px; text-align: center; color: #9ca3af;">
|
||||
Documento gerado em ${new Date().toLocaleString('pt-BR')}
|
||||
</div>
|
||||
`
|
||||
|
||||
document.body.appendChild(element)
|
||||
|
||||
// Capturar como canvas
|
||||
const canvas = await html2canvas(element, {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
backgroundColor: '#ffffff',
|
||||
})
|
||||
|
||||
document.body.removeChild(element)
|
||||
|
||||
// Converter para PDF
|
||||
const imgData = canvas.toDataURL('image/png')
|
||||
const pdf = new jsPDF({
|
||||
orientation: 'portrait',
|
||||
unit: 'mm',
|
||||
format: 'a4',
|
||||
})
|
||||
|
||||
const imgWidth = 210 // A4 width in mm
|
||||
const pageHeight = 297 // A4 height in mm
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width
|
||||
let heightLeft = imgHeight
|
||||
|
||||
let position = 0
|
||||
|
||||
while (heightLeft >= 0) {
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight)
|
||||
heightLeft -= pageHeight
|
||||
position -= pageHeight
|
||||
if (heightLeft > 0) {
|
||||
pdf.addPage()
|
||||
}
|
||||
}
|
||||
|
||||
// Download
|
||||
pdf.save(`laudo-${reportDate}-${doctorName || 'profissional'}.pdf`)
|
||||
} catch (error) {
|
||||
console.error('Erro ao gerar PDF:', error)
|
||||
alert('Erro ao gerar PDF. Tente novamente.')
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
@ -158,7 +288,7 @@ export default function LaudoPage() {
|
||||
: 'bg-gradient-to-br from-slate-50 to-slate-100'
|
||||
}`}>
|
||||
{/* Header Toolbar */}
|
||||
<div className={`sticky top-0 z-40 transition-colors duration-300 ${
|
||||
<div className={`sticky top-0 z-40 transition-colors duration-300 print:hidden ${
|
||||
isDark
|
||||
? 'bg-slate-800 border-slate-700'
|
||||
: 'bg-white border-slate-200'
|
||||
@ -221,13 +351,13 @@ export default function LaudoPage() {
|
||||
</div>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="flex justify-center py-12 px-4 min-h-[calc(100vh-80px)]">
|
||||
<div className="flex justify-center py-12 px-4 print:py-0 print:px-0 min-h-[calc(100vh-80px)] print:min-h-screen">
|
||||
{/* Document Container */}
|
||||
<div className={`w-full max-w-4xl transition-colors duration-300 shadow-2xl rounded-xl overflow-hidden ${
|
||||
<div className={`w-full max-w-4xl transition-colors duration-300 shadow-2xl rounded-xl overflow-hidden print:shadow-none print:rounded-none print:max-w-full ${
|
||||
isDark ? 'bg-slate-800' : 'bg-white'
|
||||
}`}>
|
||||
{/* Document Content */}
|
||||
<div className="p-16 space-y-8 print:p-0 print:shadow-none">
|
||||
<div className="p-16 space-y-8 print:p-12 print:space-y-6">
|
||||
|
||||
{/* Title */}
|
||||
<div className={`text-center mb-12 pb-8 border-b-2 ${
|
||||
|
||||
@ -960,8 +960,10 @@ export default function PacientePage() {
|
||||
const [searchTerm, setSearchTerm] = useState<string>('')
|
||||
const [remoteMatch, setRemoteMatch] = useState<any | null>(null)
|
||||
const [searchingRemote, setSearchingRemote] = useState<boolean>(false)
|
||||
const [sortOrder, setSortOrder] = useState<'newest' | 'oldest' | 'custom'>('newest')
|
||||
const [filterDate, setFilterDate] = useState<string>('')
|
||||
|
||||
// derived filtered list based on search term
|
||||
// derived filtered list based on search term and date filters
|
||||
const filteredReports = useMemo(() => {
|
||||
if (!reports || !Array.isArray(reports)) return []
|
||||
const qRaw = String(searchTerm || '').trim()
|
||||
@ -980,8 +982,8 @@ export default function PacientePage() {
|
||||
return [remoteMatch]
|
||||
}
|
||||
|
||||
if (!q) return reports
|
||||
return reports.filter((r: any) => {
|
||||
// Start with all reports or filtered by search
|
||||
let filtered = !q ? reports : reports.filter((r: any) => {
|
||||
try {
|
||||
const id = r.id ? String(r.id).toLowerCase() : ''
|
||||
const title = String(reportTitle(r) || '').toLowerCase()
|
||||
@ -1013,8 +1015,38 @@ export default function PacientePage() {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// Apply date filter if specified
|
||||
if (filterDate) {
|
||||
const filterDateObj = new Date(filterDate)
|
||||
filterDateObj.setHours(0, 0, 0, 0)
|
||||
|
||||
filtered = filtered.filter((r: any) => {
|
||||
const reportDateObj = new Date(r.report_date || r.created_at || Date.now())
|
||||
reportDateObj.setHours(0, 0, 0, 0)
|
||||
return reportDateObj.getTime() === filterDateObj.getTime()
|
||||
})
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
const sorted = [...filtered]
|
||||
if (sortOrder === 'newest') {
|
||||
sorted.sort((a: any, b: any) => {
|
||||
const dateA = new Date(a.report_date || a.created_at || 0).getTime()
|
||||
const dateB = new Date(b.report_date || b.created_at || 0).getTime()
|
||||
return dateB - dateA // Newest first
|
||||
})
|
||||
} else if (sortOrder === 'oldest') {
|
||||
sorted.sort((a: any, b: any) => {
|
||||
const dateA = new Date(a.report_date || a.created_at || 0).getTime()
|
||||
const dateB = new Date(b.report_date || b.created_at || 0).getTime()
|
||||
return dateA - dateB // Oldest first
|
||||
})
|
||||
}
|
||||
|
||||
return sorted
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [reports, searchTerm, doctorsMap, remoteMatch])
|
||||
}, [reports, searchTerm, doctorsMap, remoteMatch, sortOrder, filterDate])
|
||||
|
||||
// When the search term looks like an id, attempt a direct fetch using the reports API
|
||||
useEffect(() => {
|
||||
@ -1404,6 +1436,50 @@ export default function PacientePage() {
|
||||
<Button variant="ghost" onClick={() => { setSearchTerm(''); setReportsPage(1) }} className="text-xs sm:text-sm w-full sm:w-auto">Limpar</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Date filter and sort controls */}
|
||||
<div className="flex flex-col sm:flex-row gap-2 items-stretch sm:items-center flex-wrap">
|
||||
{/* Sort buttons */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={sortOrder === 'newest' ? 'default' : 'outline'}
|
||||
onClick={() => { setSortOrder('newest'); setReportsPage(1) }}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
Mais Recente
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={sortOrder === 'oldest' ? 'default' : 'outline'}
|
||||
onClick={() => { setSortOrder('oldest'); setReportsPage(1) }}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
Mais Antigo
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Date picker */}
|
||||
<div className="flex gap-2 items-center">
|
||||
<input
|
||||
type="date"
|
||||
value={filterDate}
|
||||
onChange={(e) => { setFilterDate(e.target.value); setReportsPage(1) }}
|
||||
className="text-xs sm:text-sm px-2 sm:px-3 py-1.5 sm:py-2 border border-border rounded bg-background"
|
||||
/>
|
||||
{filterDate && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => { setFilterDate(''); setReportsPage(1) }}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
✕
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loadingReports ? (
|
||||
<div className="text-center py-6 sm:py-8 text-xs sm:text-sm text-muted-foreground">{strings.carregando}</div>
|
||||
) : reportsError ? (
|
||||
|
||||
6
susconecta/package-lock.json
generated
6
susconecta/package-lock.json
generated
@ -51,6 +51,7 @@
|
||||
"embla-carousel-react": "latest",
|
||||
"framer-motion": "^12.23.24",
|
||||
"geist": "^1.3.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
"input-otp": "latest",
|
||||
"jspdf": "^3.0.3",
|
||||
"lucide-react": "^0.454.0",
|
||||
@ -4017,7 +4018,6 @@
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
@ -4386,7 +4386,6 @@
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
@ -6066,7 +6065,6 @@
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
@ -8802,7 +8800,6 @@
|
||||
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
@ -9214,7 +9211,6 @@
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
|
||||
@ -53,6 +53,7 @@
|
||||
"embla-carousel-react": "latest",
|
||||
"framer-motion": "^12.23.24",
|
||||
"geist": "^1.3.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
"input-otp": "latest",
|
||||
"jspdf": "^3.0.3",
|
||||
"lucide-react": "^0.454.0",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user