/** * UI Module * Handles all user interface interactions and rendering */ const UI = (() => { // Tab elements const tabs = { weight: { btn: document.getElementById('tab-weight'), content: document.getElementById('weight-log-content') }, meals: { btn: document.getElementById('tab-meals'), content: document.getElementById('meals-log-content') }, charts: { btn: document.getElementById('tab-charts'), content: document.getElementById('charts-content') }, settings: { btn: document.getElementById('tab-settings'), content: document.getElementById('settings-content') } }; // Form elements const forms = { weight: document.getElementById('weight-form'), meal: document.getElementById('meal-form') }; // Table elements const tables = { weight: document.getElementById('weight-entries'), meal: document.getElementById('meal-entries') }; // Initialize the UI const init = () => { // Set today's date as default for forms document.getElementById('weight-date').value = Utils.getTodayDate(); document.getElementById('meal-date').value = Utils.getTodayDate(); // Initialize tab navigation initTabs(); // Initialize form submissions initForms(); // Populate meal form for the initial date (today) populateMealFormForDate(document.getElementById('meal-date').value); // Initialize data import/export initDataManagement(); // Add notifications styles addNotificationsCSS(); }; /** * Initialize tab navigation */ const initTabs = () => { // Add click event to each tab button for (const tabKey in tabs) { const tab = tabs[tabKey]; tab.btn.addEventListener('click', () => switchTab(tabKey)); } }; /** * Switch active tab * @param {string} tabKey - Key of the tab to activate */ const switchTab = (tabKey) => { // Deactivate all tabs for (const key in tabs) { tabs[key].btn.classList.remove('active'); tabs[key].content.classList.remove('active'); } // Activate the selected tab tabs[tabKey].btn.classList.add('active'); tabs[tabKey].content.classList.add('active'); // If switching to meals tab, ensure form is populated for the current meal date if (tabKey === 'meals') { populateMealFormForDate(document.getElementById('meal-date').value); } }; /** * Initialize form submissions */ /** * Populate the meal form fields based on data for the given date. * @param {string} dateString - The date (YYYY-MM-DD) to load data for. */ const populateMealFormForDate = (dateString) => { const mealData = DataManager.getMealByDate(dateString); document.getElementById('breakfast').value = mealData?.breakfast || ''; document.getElementById('lunch').value = mealData?.lunch || ''; document.getElementById('dinner').value = mealData?.dinner || ''; document.getElementById('other-meals').value = mealData?.otherMeals || ''; }; const initForms = () => { // Weight form submission forms.weight.addEventListener('submit', (e) => { e.preventDefault(); const weightEntry = { date: document.getElementById('weight-date').value, weight: document.getElementById('weight-value').value, notes: document.getElementById('weight-notes').value }; // Validate entry const validation = Utils.validateWeightEntry(weightEntry); if (!validation.valid) { Utils.showNotification(validation.message, 'error'); return; } // Add entry to data if (DataManager.addWeight(weightEntry)) { Utils.showNotification('Weight entry saved successfully', 'success'); // Reset form document.getElementById('weight-date').value = Utils.getTodayDate(); document.getElementById('weight-value').value = ''; document.getElementById('weight-notes').value = ''; // Refresh table renderWeightTable(); // Update charts if on chart tab if (tabs.charts.content.classList.contains('active')) { Charts.renderWeightChart(); } } else { Utils.showNotification('Error saving weight entry', 'error'); } }); // Meal form submission document.getElementById('meal-date').addEventListener('change', (e) => { populateMealFormForDate(e.target.value); }); forms.meal.addEventListener('submit', (e) => { e.preventDefault(); const mealEntry = { date: document.getElementById('meal-date').value, breakfast: document.getElementById('breakfast').value, lunch: document.getElementById('lunch').value, dinner: document.getElementById('dinner').value, otherMeals: document.getElementById('other-meals').value }; // Add/update entry if (DataManager.addMeal(mealEntry)) { Utils.showNotification('Meal entries saved successfully', 'success'); // Refresh form with current date's data (which might have just been updated) populateMealFormForDate(mealEntry.date); // Refresh table renderMealTable(); } else { Utils.showNotification('Error saving meal entries', 'error'); } }); }; /** * Initialize data import/export functionality */ const initDataManagement = () => { // Export data as JSON document.getElementById('export-data').addEventListener('click', () => { DataManager.exportData(); Utils.showNotification('Data exported successfully', 'success'); }); // Import data file selection document.getElementById('import-data-file').addEventListener('change', (e) => { const fileInput = e.target; const importButton = document.getElementById('import-data'); if (fileInput.files.length > 0) { document.getElementById('file-name-display').textContent = fileInput.files[0].name; importButton.disabled = false; } else { document.getElementById('file-name-display').textContent = 'No file selected'; importButton.disabled = true; } }); // Import data from JSON document.getElementById('import-data').addEventListener('click', () => { const fileInput = document.getElementById('import-data-file'); const importMode = document.querySelector('input[name="import-mode"]:checked').value; if (fileInput.files.length === 0) { Utils.showNotification('Please select a file to import', 'error'); return; } const file = fileInput.files[0]; const reader = new FileReader(); reader.onload = (event) => { try { // First, validate that we can parse the JSON let importedData; try { importedData = JSON.parse(event.target.result); } catch (parseError) { console.error('JSON parsing error:', parseError); Utils.showNotification('Invalid JSON format. Please check the file contents.', 'error'); return; } // Then validate the structure before importing if (!importedData || typeof importedData !== 'object') { Utils.showNotification('Invalid data: File does not contain a valid data object', 'error'); return; } // Check for weights and meals arrays if (!importedData.weights || !Array.isArray(importedData.weights)) { Utils.showNotification('Invalid data: Missing weights array', 'error'); return; } if (!importedData.meals || !Array.isArray(importedData.meals)) { Utils.showNotification('Invalid data: Missing meals array', 'error'); return; } // Try to import the data if (DataManager.importData(importedData, importMode)) { Utils.showNotification('Data imported successfully', 'success'); // Refresh tables and charts renderWeightTable(); renderMealTable(); Charts.renderWeightChart(); // Reset file input fileInput.value = ''; document.getElementById('file-name-display').textContent = 'No file selected'; document.getElementById('import-data').disabled = true; } else { Utils.showNotification('Error importing data. Please check console for details.', 'error'); } } catch (error) { console.error('Error processing imported data:', error); Utils.showNotification('Error processing data: ' + (error.message || 'Unknown error'), 'error'); } }; reader.onerror = () => { Utils.showNotification('Error reading file', 'error'); }; reader.readAsText(file); }); // Export as CSV document.getElementById('export-csv').addEventListener('click', () => { DataManager.exportCSV('weights'); Utils.showNotification('Weight data exported as CSV', 'success'); }); // Copy table data to clipboard document.getElementById('copy-table-data').addEventListener('click', () => { const weights = DataManager.getWeights(); if (weights.length === 0) { Utils.showNotification('No weight data to copy', 'error'); return; } let csvContent = 'Date,Weight (kg),Notes\n'; weights.forEach(entry => { const notes = entry.notes ? `"${entry.notes.replace(/"/g, '""')}"` : ''; csvContent += `${entry.date},${entry.weight},${notes}\n`; }); if (Utils.copyToClipboard(csvContent)) { Utils.showNotification('Weight data copied to clipboard', 'success'); } else { Utils.showNotification('Failed to copy data to clipboard', 'error'); } }); // Date filter for chart document.getElementById('apply-date-filter').addEventListener('click', () => { Charts.updateDateFilter( document.getElementById('chart-start-date').value, document.getElementById('chart-end-date').value ); }); // Reset date filter document.getElementById('reset-date-filter').addEventListener('click', () => { document.getElementById('chart-start-date').value = ''; document.getElementById('chart-end-date').value = ''; Charts.updateDateFilter(null, null); }); }; /** * Render the weight entries table */ const renderWeightTable = () => { const weights = DataManager.getWeights(); tables.weight.innerHTML = ''; if (weights.length === 0) { // Show empty state const emptyRow = document.createElement('tr'); emptyRow.innerHTML = `