237 lines
8.4 KiB
JavaScript
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: '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 = '<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();
|