Initial commit
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* Main Application Module
|
||||
* Handles UI interactions and coordinates between modules
|
||||
*/
|
||||
|
||||
class HealthAnalyticsApp {
|
||||
constructor() {
|
||||
// Initialize UI elements
|
||||
this.initializeUI();
|
||||
|
||||
// Add event listeners
|
||||
this.addEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize UI elements
|
||||
*/
|
||||
initializeUI() {
|
||||
// File upload elements
|
||||
this.metricsFileInput = document.getElementById('metrics-file');
|
||||
this.workoutsFileInput = document.getElementById('workouts-file');
|
||||
this.metricsFilename = document.getElementById('metrics-filename');
|
||||
this.workoutsFilename = document.getElementById('workouts-filename');
|
||||
|
||||
// Analysis options elements
|
||||
this.analysisFocusSelect = document.getElementById('analysis-focus');
|
||||
this.recommendationStyleSelect = document.getElementById('recommendation-style');
|
||||
this.reportDetailSelect = document.getElementById('report-detail');
|
||||
this.customPromptTextarea = document.getElementById('custom-prompt');
|
||||
this.showGptInputCheckbox = document.getElementById('show-gpt-input');
|
||||
|
||||
// Buttons
|
||||
this.useSampleDataButton = document.getElementById('use-sample-data');
|
||||
this.generateReportButton = document.getElementById('generate-report');
|
||||
this.downloadReportButton = document.getElementById('download-report');
|
||||
this.newAnalysisButton = document.getElementById('new-analysis');
|
||||
|
||||
// Report section
|
||||
this.reportSection = document.querySelector('.report-section');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listeners to UI elements
|
||||
*/
|
||||
addEventListeners() {
|
||||
// File input change events
|
||||
this.metricsFileInput.addEventListener('change', (e) => this.handleFileInputChange(e, this.metricsFilename));
|
||||
this.workoutsFileInput.addEventListener('change', (e) => this.handleFileInputChange(e, this.workoutsFilename));
|
||||
|
||||
// Button click events
|
||||
this.useSampleDataButton.addEventListener('click', () => this.handleUseSampleData());
|
||||
this.generateReportButton.addEventListener('click', () => this.handleGenerateReport());
|
||||
this.downloadReportButton.addEventListener('click', () => this.handleDownloadReport());
|
||||
this.newAnalysisButton.addEventListener('click', () => this.handleNewAnalysis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle file input change
|
||||
* @param {Event} event - Change event
|
||||
* @param {HTMLElement} filenameElement - Element to display filename
|
||||
*/
|
||||
handleFileInputChange(event, filenameElement) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
filenameElement.textContent = file.name;
|
||||
} else {
|
||||
filenameElement.textContent = 'No file selected';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle use sample data button click
|
||||
*/
|
||||
async handleUseSampleData() {
|
||||
try {
|
||||
// Disable the button during loading
|
||||
this.useSampleDataButton.disabled = true;
|
||||
this.useSampleDataButton.textContent = 'Loading...';
|
||||
|
||||
// Load sample data
|
||||
await dataProcessor.loadSampleData();
|
||||
|
||||
// Update UI to show sample data is loaded
|
||||
this.metricsFilename.textContent = 'Sample metrics.csv loaded';
|
||||
this.workoutsFilename.textContent = 'Sample workouts.csv loaded';
|
||||
|
||||
// Show success message
|
||||
this.showNotification('Sample data loaded successfully!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Error loading sample data:', error);
|
||||
this.showNotification('Error loading sample data: ' + error.message, 'error');
|
||||
} finally {
|
||||
// Re-enable the button
|
||||
this.useSampleDataButton.disabled = false;
|
||||
this.useSampleDataButton.textContent = 'Use Sample Data';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle generate report button click
|
||||
*/
|
||||
async handleGenerateReport() {
|
||||
try {
|
||||
// Check if files are selected but not processed
|
||||
if ((this.metricsFileInput.files.length > 0 || this.workoutsFileInput.files.length > 0) &&
|
||||
(!dataProcessor.metricsData || !dataProcessor.workoutsData)) {
|
||||
// Process the uploaded files
|
||||
await this.processUploadedFiles();
|
||||
}
|
||||
// Check if data is loaded
|
||||
else if (!this.isDataLoaded()) {
|
||||
// If no files are selected, try to use sample data
|
||||
if (this.metricsFilename.textContent === 'No file selected' &&
|
||||
this.workoutsFilename.textContent === 'No file selected') {
|
||||
await this.handleUseSampleData();
|
||||
} else {
|
||||
throw new Error('Please upload both metrics and workouts files or use sample data.');
|
||||
}
|
||||
}
|
||||
|
||||
// Disable the button during generation
|
||||
this.generateReportButton.disabled = true;
|
||||
|
||||
// Get analysis options
|
||||
const options = this.getAnalysisOptions();
|
||||
|
||||
// Prepare data for analysis
|
||||
const data = dataProcessor.prepareDataForAnalysis();
|
||||
|
||||
// Generate the report
|
||||
await reportGenerator.generateReport(data, options);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating report:', error);
|
||||
this.showNotification('Error generating report: ' + error.message, 'error');
|
||||
} finally {
|
||||
// Re-enable the button
|
||||
this.generateReportButton.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle download report button click
|
||||
*/
|
||||
async handleDownloadReport() {
|
||||
try {
|
||||
await reportGenerator.generatePDF();
|
||||
} catch (error) {
|
||||
console.error('Error downloading report:', error);
|
||||
this.showNotification('Error downloading report: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle new analysis button click
|
||||
*/
|
||||
handleNewAnalysis() {
|
||||
// Reset the report view
|
||||
reportGenerator.resetReport();
|
||||
|
||||
// Scroll to the top of the page
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data is loaded
|
||||
* @returns {boolean} - Whether data is loaded
|
||||
*/
|
||||
isDataLoaded() {
|
||||
return dataProcessor.metricsData !== null && dataProcessor.workoutsData !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get analysis options from UI
|
||||
* @returns {Object} - Analysis options
|
||||
*/
|
||||
getAnalysisOptions() {
|
||||
// Get API key from local storage or prompt
|
||||
let apiKey = localStorage.getItem('openai_api_key');
|
||||
|
||||
// If no API key is stored, prompt the user
|
||||
if (!apiKey) {
|
||||
apiKey = this.promptForAPIKey();
|
||||
}
|
||||
|
||||
return {
|
||||
analysisFocus: this.analysisFocusSelect.value,
|
||||
recommendationStyle: this.recommendationStyleSelect.value,
|
||||
reportDetail: this.reportDetailSelect.value,
|
||||
customPrompt: this.customPromptTextarea.value,
|
||||
showGptInput: this.showGptInputCheckbox.checked,
|
||||
apiKey: apiKey
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt user for OpenAI API key
|
||||
* @returns {string} - API key
|
||||
*/
|
||||
promptForAPIKey() {
|
||||
// In a real application, you would use a modal dialog
|
||||
// For this demo, we'll use a simple prompt
|
||||
const apiKey = prompt(
|
||||
'Enter your OpenAI API key to generate personalized recommendations.\n' +
|
||||
'If you don\'t have an API key, leave this blank to use demo mode.',
|
||||
''
|
||||
);
|
||||
|
||||
// Ask if the user wants to save the API key
|
||||
if (apiKey && apiKey.trim()) {
|
||||
const saveKey = confirm('Would you like to save this API key for future use?');
|
||||
if (saveKey) {
|
||||
localStorage.setItem('openai_api_key', apiKey);
|
||||
}
|
||||
}
|
||||
|
||||
return apiKey || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process uploaded files
|
||||
* @returns {Promise} - Promise resolving when files are processed
|
||||
*/
|
||||
async processUploadedFiles() {
|
||||
const metricsFile = this.metricsFileInput.files[0];
|
||||
const workoutsFile = this.workoutsFileInput.files[0];
|
||||
|
||||
if (!metricsFile || !workoutsFile) {
|
||||
throw new Error('Please upload both metrics and workouts files.');
|
||||
}
|
||||
|
||||
return await dataProcessor.processFiles(metricsFile, workoutsFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification message
|
||||
* @param {string} message - Message to display
|
||||
* @param {string} type - Notification type (success, error, warning)
|
||||
*/
|
||||
showNotification(message, type = 'info') {
|
||||
// Create notification element
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification ${type}`;
|
||||
notification.textContent = message;
|
||||
|
||||
// Add close button
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.className = 'notification-close';
|
||||
closeButton.innerHTML = '×';
|
||||
closeButton.addEventListener('click', () => {
|
||||
document.body.removeChild(notification);
|
||||
});
|
||||
|
||||
notification.appendChild(closeButton);
|
||||
|
||||
// Add to document
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(notification)) {
|
||||
document.body.removeChild(notification);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the application when the DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Create notification styles dynamically
|
||||
const notificationStyles = document.createElement('style');
|
||||
notificationStyles.textContent = `
|
||||
.notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 15px 20px;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
max-width: 300px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.notification.success {
|
||||
background-color: #2ecc71;
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
background-color: #e74c3c;
|
||||
}
|
||||
|
||||
.notification.warning {
|
||||
background-color: #f39c12;
|
||||
}
|
||||
|
||||
.notification.info {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.notification-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 10px;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(notificationStyles);
|
||||
|
||||
// Initialize the app
|
||||
window.app = new HealthAnalyticsApp();
|
||||
});
|
||||
Reference in New Issue
Block a user