Files

237 lines
8.4 KiB
JavaScript

/**
* 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 = `
<h3>GPT-4.1 Input Data</h3>
<span class="toggle-icon">▼</span>
`;
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 = `
<div class="error-message">
<h3>Error Generating Report</h3>
<p>${errorMessage}</p>
<p>Please try again or check your settings.</p>
</div>
`;
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 = '<i class="fas fa-spinner fa-spin"></i> 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: 'Sport-Science-Coach-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 = '<i class="fas fa-download"></i> 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();