Range of Motion (ROM) Calculator

Range of Motion (ROM) Calculator

Please enter start and end angles to calculate Range of Motion.

'; resultsSection.style.display = 'block'; } } async function generatePdf() { const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' }); const patientName = patientNameInput.value || 'N/A'; const assessmentDate = assessmentDateInput.value ? new Date(assessmentDateInput.value).toLocaleDateString() : 'N/A'; // --- PDF STYLING & STRUCTURE --- const pageHeight = doc.internal.pageSize.height; const pageWidth = doc.internal.pageSize.width; const margin = 15; let y = margin; // 1. HEADER const logoSvg = ``; doc.addSvgAsImage(logoSvg, margin, margin, 10, 10); doc.setFont('helvetica', 'bold'); doc.setFontSize(18); doc.setTextColor(40, 40, 40); doc.text('Range of Motion (ROM) Report', margin + 12, margin + 7); doc.setDrawColor(220, 220, 220); doc.line(margin, margin + 15, pageWidth - margin, margin + 15); y = margin + 25; // 2. PATIENT INFO doc.setFont('helvetica', 'bold'); doc.setFontSize(11); doc.text('Patient Name:', margin, y); doc.setFont('helvetica', 'normal'); doc.text(patientName, margin + 35, y); doc.setFont('helvetica', 'bold'); doc.text('Assessment Date:', pageWidth / 2, y); doc.setFont('helvetica', 'normal'); doc.text(assessmentDate, pageWidth / 2 + 35, y); y += 15; // 3. RESULTS TABLE const tableHeaders = ['Joint & Movement', 'Measured ROM', 'Normal ROM', 'Status']; const colWidths = [70, 35, 35, 35]; // Draw table header doc.setFillColor(238, 242, 255); // A light indigo doc.rect(margin, y, pageWidth - 2 * margin, 10, 'F'); doc.setFont('helvetica', 'bold'); doc.setTextColor(55, 65, 81); // gray-700 doc.setFontSize(10); let currentX = margin + 2; tableHeaders.forEach((header, i) => { doc.text(header, currentX, y + 7); currentX += colWidths[i]; }); y += 10; // Draw table rows doc.setFont('helvetica', 'normal'); doc.setFontSize(9); doc.setTextColor(40, 40, 40); for (const joint in allResultsData) { for (const movement in allResultsData[joint]) { if (y > pageHeight - 25) { // Check for page break doc.addPage(); y = margin; } const data = allResultsData[joint][movement]; const rowData = [ `${joint} - ${movement}`, `${data.calculated.toFixed(1)} ${data.unit}`, `${data.normal} ${data.unit}`, data.status ]; // Set status color if (data.status === 'Limited') doc.setTextColor(220, 38, 38); // red-600 else if (data.status === 'Hypermobile') doc.setTextColor(217, 119, 6); // amber-600 else doc.setTextColor(22, 163, 74); // green-600 // Draw row text currentX = margin + 2; rowData.forEach((text, i) => { // Only draw status in color if (i < 3) doc.setTextColor(40, 40, 40); doc.text(text, currentX, y + 7); currentX += colWidths[i]; }); doc.setDrawColor(220, 220, 220); doc.line(margin, y + 10, pageWidth - margin, y + 10); y += 10; } } // 4. FOOTER doc.setFontSize(8); doc.setTextColor(150); doc.text(`Report generated on ${new Date().toLocaleString()}. This is not a substitute for professional medical advice.`, margin, pageHeight - 10); doc.save(`ROM_Report_${patientName.replace(/ /g, '_')}.pdf`); } // --- EVENT LISTENERS --- tabButtonsContainer.addEventListener('click', (e) => e.target.classList.contains('tab-button') && switchTab(parseInt(e.target.dataset.index))); tabContentContainer.addEventListener('click', (e) => e.target.classList.contains('calculate-btn') && calculateAndDisplayROM(e.target.dataset.joint)); prevTabBtn.addEventListener('click', () => currentTabIndex > 0 && switchTab(currentTabIndex - 1)); nextTabBtn.addEventListener('click', () => currentTabIndex < jointNames.length - 1 && switchTab(currentTabIndex + 1)); downloadPdfBtn.addEventListener('click', generatePdf); // --- RUN INITIALIZATION --- initializeUI(); switchTab(0); });
Scroll to Top