Initial commit
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* 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();
|
||||
Reference in New Issue
Block a user