clinicpal / src /hooks /use-note-export.ts
Vrda's picture
Deploy ClinIcPal frontend
9bc2f29 verified
'use client';
import { useCallback } from 'react';
export type NoteExportFormat = 'pdf' | 'docx' | 'txt';
export function useNoteExport() {
// Export to plain text
const exportToText = useCallback((noteText: string): void => {
downloadFile(
noteText,
`clinical-note-${new Date().toISOString().split('T')[0]}.txt`,
'text/plain'
);
}, []);
// Export to PDF (using print dialog)
const exportToPDF = useCallback((noteText: string): void => {
const printWindow = window.open('', '', 'width=800,height=600');
if (!printWindow) {
alert('Please allow popups to export to PDF');
return;
}
const html = generatePrintHTML(noteText);
printWindow.document.write(html);
printWindow.document.close();
printWindow.onload = () => {
printWindow.focus();
printWindow.print();
};
}, []);
// Export to DOCX (using docx library)
const exportToDocx = useCallback(async (noteText: string): Promise<void> => {
const { Document, Packer, Paragraph, TextRun, AlignmentType } = await import('docx');
// Split text into paragraphs
const paragraphs = noteText.split('\n').map(line =>
new Paragraph({
children: [
new TextRun({
text: line || ' ', // Add space for empty lines to preserve spacing
font: 'Arial',
size: 22, // 11pt in half-points
}),
],
spacing: {
after: 200, // spacing after paragraph
},
})
);
// Create document with header
const doc = new Document({
sections: [{
properties: {},
children: [
new Paragraph({
children: [
new TextRun({
text: 'Clinical Note',
bold: true,
font: 'Arial',
size: 28, // 14pt in half-points
}),
],
alignment: AlignmentType.CENTER,
spacing: {
after: 400,
},
}),
...paragraphs,
],
}],
});
// Generate and download
const blob = await Packer.toBlob(doc);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `clinical-note-${new Date().toISOString().split('T')[0]}.docx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}, []);
// Main export function
const exportNote = useCallback(
async (format: NoteExportFormat, noteText: string) => {
if (!noteText.trim()) {
return;
}
switch (format) {
case 'txt':
exportToText(noteText);
break;
case 'pdf':
exportToPDF(noteText);
break;
case 'docx':
await exportToDocx(noteText);
break;
}
},
[exportToText, exportToPDF, exportToDocx]
);
return { exportNote };
}
// Utility function to download file
function downloadFile(content: string, filename: string, mimeType: string) {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
// Generate print-friendly HTML for PDF export
function generatePrintHTML(noteText: string): string {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Clinical Note</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
@page {
size: letter;
margin: 1in;
}
body {
font-family: Arial, Helvetica, sans-serif;
line-height: 1.6;
color: #000;
padding: 2rem;
max-width: 8.5in;
margin: 0 auto;
}
.header {
text-align: center;
font-size: 14pt;
font-weight: bold;
color: #000;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid #ccc;
}
.note-content {
white-space: pre-wrap;
word-wrap: break-word;
font-family: Arial, Helvetica, sans-serif;
font-size: 11pt;
line-height: 1.6;
}
.footer {
margin-top: 3rem;
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
text-align: center;
color: #9ca3af;
font-size: 0.875rem;
}
@media print {
body { padding: 0; }
}
</style>
</head>
<body>
<div class="header">Clinical Note</div>
<div class="note-content">${escapeHtml(noteText)}</div>
<div class="footer">
Generated on ${new Date().toLocaleDateString()} • Clinical Error Detector
</div>
</body>
</html>
`;
}
function escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}