Overview
DoctorSoft+ provides a comprehensive patient reporting system that generates detailed, printable reports containing patient demographics, medical information, contact details, and more.
PatientReport component
The PatientReport component (src/components/PatientReport.tsx) renders formatted patient information for viewing and printing.
Basic usage
import { PatientReport } from '../components/PatientReport' ;
function ReportPage () {
return (
< PatientReport
patientId = "patient-uuid"
isModalView = { false }
/>
);
}
Component props
Required. The unique identifier of the patient whose report to display.
Optional boolean. When true, renders the report in modal layout without page wrapper. Default: false.
Optional callback function. Called when the close button is clicked in modal view.
Optional patient data object. If provided, uses this data instead of fetching from API. Useful for performance optimization.
Report sections
Patient reports are organized into comprehensive sections:
Patient identification
Displays key identifying information:
< div className = "text-center" >
< div className = "w-16 h-16 rounded-full" >
< User className = "w-8 h-8" />
</ div >
< h2 > { patient . Nombre } { patient . Paterno } { patient . Materno } </ h2 >
< div >
< span > Folio: { patient . Folio } </ span >
< span > • { age . formatted } </ span >
< span > • { patient . Sexo } </ span >
< span > • Nacido el { formatDate ( patient . FechaNacimiento ) } </ span >
</ div >
</ div >
Includes:
Full name
Date of birth
Age (calculated)
Gender
Marital status
CURP (Mexican ID)
RFC (Tax ID)
Nationality
Birth state
Displays:
Phone number
Email address
Emergency contact
Street address
Neighborhood/settlement
Postal code
City/town
Municipality
State
Contains:
Blood type
Allergies (displayed as comma-separated list)
Occupation
Insurance provider
Patient type
Referring physician
Responsible party
Includes:
Religion
Indigenous language
Ethnic group
Disabilities
Additional observations
Displays free-form notes and observations:
{ patient . Observaciones && (
< InfoSection title = "Observaciones Adicionales" >
< div className = "p-4 rounded-lg" >
< p > { patient . Observaciones } </ p >
</ div >
</ InfoSection >
)}
Report layouts
Full page report
Default layout for dedicated report pages:
< PatientReport
patientId = { patientId }
isModalView = { false }
/>
Features:
Full page layout with header
Navigation controls (Back button)
Print button in header
Print-optimized styling
Modal report
Compact layout for displaying in modals:
< Modal isOpen = { showReport } onClose = { handleClose } >
< PatientReport
patientId = { patientId }
isModalView = { true }
onClose = { handleClose }
patientData = { patient }
/>
</ Modal >
Features:
Compact layout without page wrapper
Close button
Print button
No background in print mode
Printing reports
Reports are optimized for printing with special print styles:
Print functionality
const handlePrint = () => {
// Add delay to ensure DOM is ready
setTimeout (() => {
window . print ();
}, 100 );
};
< button onClick = { handlePrint } className = "print:hidden" >
< Printer className = "h-5 w-5 mr-2" />
Imprimir
</ button >
The print button is automatically hidden when printing using the print:hidden class.
Print styles
Reports use special styling for print media:
Print-specific classes
className = "text-2xl font-bold print:text-black"
className = "rounded-lg shadow-lg print:shadow-none print:rounded-none"
className = "flex gap-2 print:hidden"
className = "hidden print:block"
Print optimizations
Remove interactive elements
Buttons, navigation, and controls are hidden in print: .print \: hidden {
display : none ;
}
Simplify colors
Theme colors are replaced with black text for better printing: className = "print:text-black print:border-black"
Remove shadows and effects
Decorative elements are removed: className = "shadow-lg print:shadow-none"
className = "rounded-lg print:rounded-none"
Optimize layout
Grid layouts are preserved with print-specific classes: className = "grid grid-cols-3 print:!grid print:!grid-cols-2"
Print event handlers
For modal views, special print event handling ensures proper rendering:
useEffect (() => {
if ( ! isModalView ) return ;
const handleBeforePrint = () => {
document . body . classList . add ( 'is-printing-modal' );
};
const handleAfterPrint = () => {
document . body . classList . remove ( 'is-printing-modal' );
};
window . addEventListener ( 'beforeprint' , handleBeforePrint );
window . addEventListener ( 'afterprint' , handleAfterPrint );
return () => {
window . removeEventListener ( 'beforeprint' , handleBeforePrint );
window . removeEventListener ( 'afterprint' , handleAfterPrint );
document . body . classList . remove ( 'is-printing-modal' );
};
}, [ isModalView ]);
Report page implementation
The PatientReportPage component provides a standalone report page:
import { PatientReportPage } from '../pages/PatientReportPage' ;
// In your router
< Route path = "/patients/:id/report" element = { < PatientReportPage /> } />
Auto-print feature
Reports can automatically print when loaded with a query parameter:
// Navigate to auto-printing report
navigate ( `/patients/ ${ patientId } /report?print=true` );
Implementation:
useEffect (() => {
const urlParams = new URLSearchParams ( window . location . search );
const shouldAutoPrint = urlParams . get ( 'print' ) === 'true' ;
if ( shouldAutoPrint && patient && ! loading && ! autoPrintTriggered ) {
setAutoPrintTriggered ( true );
// Delay to ensure content is rendered
setTimeout (() => {
window . print ();
}, 1000 );
}
}, [ patient , loading , autoPrintTriggered ]);
The 1-second delay ensures all content is fully rendered before the print dialog appears.
Reusable components
Reports use reusable components for consistent formatting:
InfoSection component
const InfoSection = ({ title , children }) => {
const { currentTheme } = useTheme ();
return (
< div className = "mb-6" >
< h3
className = "text-lg font-semibold mb-3 pb-2 border-b-2"
style = { {
color: currentTheme . colors . primary ,
borderColor: currentTheme . colors . primary ,
} }
>
{ title }
</ h3 >
< div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" >
{ children }
</ div >
</ div >
);
};
InfoField component
const InfoField = ({ label , value , fullWidth = false }) => {
const { currentTheme } = useTheme ();
return (
< div className = { fullWidth && 'md:col-span-2 lg:col-span-3' } >
< dt style = { { color: currentTheme . colors . textSecondary } } >
{ label }
</ dt >
< dd style = { { color: currentTheme . colors . text } } >
{ value || 'No especificado' }
</ dd >
</ div >
);
};
Reports use locale-aware date formatting:
import { format , parseISO } from 'date-fns' ;
import { es } from 'date-fns/locale' ;
const formatDate = ( dateString : string ) => {
try {
const date = parseISO ( dateString );
return format ( date , "d 'de' MMMM 'de' yyyy" , { locale: es });
} catch {
return dateString ;
}
};
// Example output: "15 de marzo de 2024"
Report generation timestamp
< p >
Generado el { format ( new Date (), "d 'de' MMMM 'de' yyyy 'a las' HH:mm" , { locale: es }) }
</ p >
// Example: "Generado el 15 de marzo de 2024 a las 14:30"
Age calculation
Reports automatically calculate and display patient age:
import { calculateAge } from '../utils/dateUtils' ;
const age = patient . FechaNacimiento ? calculateAge ( patient . FechaNacimiento ) : null ;
// Display formatted age
{ age && < span > { age . formatted } </ span > }
// Example: "42 años"
Loading and error states
Loading state
if ( loading || authLoading ) {
return (
< div className = "flex items-center justify-center min-h-screen" >
< div className = "animate-spin rounded-full h-8 w-8 border-b-2" />
< span > Cargando datos del paciente... </ span >
</ div >
);
}
Error state
if ( error ) {
return (
< div className = "p-4 rounded-md border-l-4 bg-red-50 border-red-600" >
< p > { error } </ p >
< button onClick = { () => window . history . back () } >
< ArrowLeft className = "h-5 w-5" />
Volver
</ button >
</ div >
);
}
No patient found
if ( ! patient ) {
return (
< div >
< p > Paciente no encontrado </ p >
< button onClick = { () => window . history . back () } > Volver </ button >
</ div >
);
}
Optimize report loading with data pre-fetching:
// Pre-fetch patient data before showing report
const [ patientData , setPatientData ] = useState ( null );
useEffect (() => {
const fetchPatient = async () => {
const data = await api . patients . getById ( patientId );
setPatientData ( data );
};
fetchPatient ();
}, [ patientId ]);
// Pass pre-fetched data to report
< PatientReport
patientId = { patientId }
patientData = { patientData }
isModalView = { true }
/>
Pre-fetching patient data eliminates the loading delay when opening reports in modals.
Add a professional footer for printed reports:
< div className = "hidden print:block mt-12 pt-6 border-t border-gray-300" >
< div className = "text-center text-sm text-gray-600" >
< p > DoctorSoft+ - Sistema de Gestión Médica </ p >
< p >
Ficha generada el { format ( new Date (), "d 'de' MMMM 'de' yyyy 'a las' HH:mm" , { locale: es }) }
</ p >
</ div >
</ div >
The footer is only visible when printing, not on screen.
Best practices
Validate authentication
Always check user authentication before displaying patient reports: const { user , loading : authLoading } = useAuth ();
if ( ! authLoading && ! user ) {
setError ( 'Usuario no autenticado para ver el informe.' );
return ;
}
Handle missing data gracefully
Display “No especificado” for empty fields instead of showing null or undefined: < dd > { value || 'No especificado' } </ dd >
Use print delays
Add small delays before auto-printing to ensure content is rendered: setTimeout (() => window . print (), 1000 );
Optimize print styles
Use print: Tailwind classes for print-specific styling: className = "shadow-lg print:shadow-none"
className = "text-blue-600 print:text-black"
Pre-fetch data for modals
When showing reports in modals, pre-fetch patient data to avoid loading states.
Navigation integration
Integrate reports into your application navigation:
import { useNavigate } from 'react-router-dom' ;
// Navigate to report page
const navigate = useNavigate ();
navigate ( `/patients/ ${ patientId } /report` );
// Navigate to report with auto-print
navigate ( `/patients/ ${ patientId } /report?print=true` );
// Open in new tab for printing
window . open (
`/patients/ ${ patientId } /report?print=true` ,
'_blank' ,
'noopener,noreferrer'
);
Themed report styling
Reports respect the active theme for on-screen viewing:
const { currentTheme } = useTheme ();
< div
style = { {
background: currentTheme . colors . surface ,
color: currentTheme . colors . text ,
borderColor: currentTheme . colors . border ,
} }
>
Report content
</ div >
Theme colors are automatically converted to print-friendly black and white when printing.