/** * Report Generator Module * Handles the generation and display of health reports */ class ReportGenerator { constructor() { this.reportContainer = document.getElementById('report-container'); this.loadingIndicator = document.querySelector('.loading-indicator'); this.reportSection = document.querySelector('.report-section'); } /** * Generate a health report based on data and options * @param {Object} data - Processed health and workout data * @param {Object} options - Analysis options * @returns {Promise} - Promise resolving when report is generated */ async generateReport(data, options) { try { // Show loading indicator this.showLoading(); let result; // Check if we have an API key if (options.apiKey && options.apiKey.trim()) { // Set the API key openaiService.setApiKey(options.apiKey); // Generate recommendations using the OpenAI API result = await openaiService.generateRecommendations(data, options); } else { // Generate demo recommendations without API result = await openaiService.generateDemoRecommendations(data, options); } // Display the report this.displayReport(result.content, options.showGptInput ? result.rawInput : null); return result.content; } catch (error) { this.displayError(error.message); throw error; } finally { // Hide loading indicator this.hideLoading(); } } /** * Display the generated report * @param {string} reportContent - HTML content of the report * @param {Object|null} rawInputData - Raw input data sent to GPT-4.1 */ displayReport(reportContent, rawInputData = null) { // Set the report content this.reportContainer.innerHTML = reportContent; // Create charts section const chartsSection = document.createElement('div'); chartsSection.className = 'charts-section'; const chartsSectionTitle = document.createElement('h2'); chartsSectionTitle.textContent = 'Data Visualizations'; chartsSection.appendChild(chartsSectionTitle); const chartsSectionDescription = document.createElement('p'); chartsSectionDescription.textContent = 'Interactive visualizations of your health and fitness data.'; chartsSection.appendChild(chartsSectionDescription); // Add charts section to the report this.reportContainer.appendChild(chartsSection); // Generate charts based on the data if (dataProcessor.metricsData && dataProcessor.workoutsData) { const data = { metrics: dataProcessor.metricsData, workouts: dataProcessor.workoutsData }; // Create charts chartsManager.createCharts(data, chartsSection); } // Add raw input data section if available if (rawInputData) { const rawDataSection = document.createElement('div'); rawDataSection.className = 'raw-data-section'; const rawDataToggle = document.createElement('div'); rawDataToggle.className = 'raw-data-toggle'; rawDataToggle.innerHTML = `

GPT-4.1 Input Data

`; const rawDataContainer = document.createElement('div'); rawDataContainer.className = 'raw-data-container'; const systemPromptPre = document.createElement('pre'); systemPromptPre.textContent = `/* System Prompt */\n${rawInputData.systemPrompt}`; const userPromptPre = document.createElement('pre'); userPromptPre.textContent = `\n\n/* User Prompt */\n${rawInputData.userPrompt}`; rawDataContainer.appendChild(systemPromptPre); rawDataContainer.appendChild(userPromptPre); rawDataSection.appendChild(rawDataToggle); rawDataSection.appendChild(rawDataContainer); this.reportContainer.appendChild(rawDataSection); // Add toggle functionality rawDataToggle.addEventListener('click', () => { rawDataContainer.classList.toggle('hidden'); rawDataToggle.querySelector('.toggle-icon').textContent = rawDataContainer.classList.contains('hidden') ? '▶' : '▼'; }); } // Show the report section this.reportSection.classList.remove('hidden'); // Scroll to the report section this.reportSection.scrollIntoView({ behavior: 'smooth' }); } /** * Display an error message * @param {string} errorMessage - Error message to display */ displayError(errorMessage) { const errorHTML = `

Error Generating Report

${errorMessage}

Please try again or check your settings.

`; this.reportContainer.innerHTML = errorHTML; this.reportSection.classList.remove('hidden'); } /** * Show the loading indicator */ showLoading() { this.loadingIndicator.classList.remove('hidden'); this.reportContainer.innerHTML = ''; this.reportSection.classList.remove('hidden'); } /** * Hide the loading indicator */ hideLoading() { this.loadingIndicator.classList.add('hidden'); } /** * Generate a PDF from the report * @returns {Promise} - Promise resolving when PDF is generated */ async generatePDF() { try { // Show a loading indicator or message const downloadButton = document.getElementById('download-report'); const originalText = downloadButton.innerHTML; downloadButton.innerHTML = ' Generating PDF...'; downloadButton.disabled = true; // Add PDF-specific class to constrain chart widths this.reportContainer.classList.add('pdf-export'); // Wait a moment to ensure all charts are fully rendered with new styles await new Promise(resolve => setTimeout(resolve, 800)); // Configure PDF options const element = this.reportContainer; const opt = { margin: [10, 10], filename: 'health-analytics-report.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2, useCORS: true, logging: false }, jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' } }; // Generate and save the PDF await html2pdf().set(opt).from(element).save(); // Remove PDF-specific class this.reportContainer.classList.remove('pdf-export'); // Restore button state downloadButton.innerHTML = originalText; downloadButton.disabled = false; return true; } catch (error) { console.error('Error generating PDF:', error); alert('Error generating PDF: ' + error.message); // Remove PDF-specific class in case of error this.reportContainer.classList.remove('pdf-export'); // Restore button state const downloadButton = document.getElementById('download-report'); downloadButton.innerHTML = ' Download PDF'; downloadButton.disabled = false; return false; } } /** * Reset the report view */ resetReport() { this.reportContainer.innerHTML = ''; this.reportSection.classList.add('hidden'); } } // Create a global instance const reportGenerator = new ReportGenerator();