Skip to main content

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>

Personal information

Includes:
  • Full name
  • Date of birth
  • Age (calculated)
  • Gender
  • Marital status
  • CURP (Mexican ID)
  • RFC (Tax ID)
  • Nationality
  • Birth state

Contact information

Displays:
  • Phone number
  • Email address
  • Emergency contact
  • Street address
  • Neighborhood/settlement
  • Postal code
  • City/town
  • Municipality
  • State

Medical information

Contains:
  • Blood type
  • Allergies (displayed as comma-separated list)
  • Occupation
  • Insurance provider
  • Patient type
  • Referring physician
  • Responsible party

Cultural and social information

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
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:
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.
Reports use special styling for print media:
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"
1

Remove interactive elements

Buttons, navigation, and controls are hidden in print:
.print\:hidden {
  display: none;
}
2

Simplify colors

Theme colors are replaced with black text for better printing:
className="print:text-black print:border-black"
3

Remove shadows and effects

Decorative elements are removed:
className="shadow-lg print:shadow-none"
className="rounded-lg print:rounded-none"
4

Optimize layout

Grid layouts are preserved with print-specific classes:
className="grid grid-cols-3 print:!grid print:!grid-cols-2"
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>
  );
};

Date formatting

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>
  );
}

Performance optimization

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

1

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;
}
2

Handle missing data gracefully

Display “No especificado” for empty fields instead of showing null or undefined:
<dd>{value || 'No especificado'}</dd>
3

Use print delays

Add small delays before auto-printing to ensure content is rendered:
setTimeout(() => window.print(), 1000);
4

Optimize print styles

Use print: Tailwind classes for print-specific styling:
className="shadow-lg print:shadow-none"
className="text-blue-600 print:text-black"
5

Pre-fetch data for modals

When showing reports in modals, pre-fetch patient data to avoid loading states.
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.

Build docs developers (and LLMs) love