|
@@ -516,9 +516,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
if (totalProEl) totalProEl.textContent = '0';
|
|
if (totalProEl) totalProEl.textContent = '0';
|
|
|
if (totalFatEl) totalFatEl.textContent = '0';
|
|
if (totalFatEl) totalFatEl.textContent = '0';
|
|
|
if (totalCarbEl) totalCarbEl.textContent = '0';
|
|
if (totalCarbEl) totalCarbEl.textContent = '0';
|
|
|
-
|
|
|
|
|
- const saveBtn = document.getElementById('save-meal-btn');
|
|
|
|
|
- if (saveBtn) saveBtn.style.display = 'none';
|
|
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -557,10 +554,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
if (totalProEl) animateValue(totalProEl, data.macros.protein_g);
|
|
if (totalProEl) animateValue(totalProEl, data.macros.protein_g);
|
|
|
if (totalFatEl) animateValue(totalFatEl, data.macros.fat_g);
|
|
if (totalFatEl) animateValue(totalFatEl, data.macros.fat_g);
|
|
|
if (totalCarbEl) animateValue(totalCarbEl, data.macros.carbs_g);
|
|
if (totalCarbEl) animateValue(totalCarbEl, data.macros.carbs_g);
|
|
|
-
|
|
|
|
|
- // Show Save button (Sprint 8)
|
|
|
|
|
- const saveBtn = document.getElementById('save-meal-btn');
|
|
|
|
|
- if (saveBtn) saveBtn.style.display = 'flex';
|
|
|
|
|
} else {
|
|
} else {
|
|
|
if (totalsBanner) totalsBanner.classList.add('has-error');
|
|
if (totalsBanner) totalsBanner.classList.add('has-error');
|
|
|
}
|
|
}
|
|
@@ -883,237 +876,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
|
|
// Initialize state
|
|
// Initialize state
|
|
|
sendBtn.disabled = true;
|
|
sendBtn.disabled = true;
|
|
|
-
|
|
|
|
|
- // --- Saved Meals Logic (Sprint 8) ---
|
|
|
|
|
- const saveMealModal = document.getElementById('save-meal-modal');
|
|
|
|
|
- const openSaveModalBtn = document.getElementById('save-meal-btn');
|
|
|
|
|
- const cancelSaveBtn = document.getElementById('cancel-save-btn');
|
|
|
|
|
- const confirmSaveBtn = document.getElementById('confirm-save-btn');
|
|
|
|
|
- const mealNameInput = document.getElementById('meal-name-input');
|
|
|
|
|
- const saveMealError = document.getElementById('save-meal-error');
|
|
|
|
|
-
|
|
|
|
|
- if (openSaveModalBtn) {
|
|
|
|
|
- openSaveModalBtn.addEventListener('click', (e) => {
|
|
|
|
|
- e.stopPropagation();
|
|
|
|
|
- if (currentMealItems.length === 0) return;
|
|
|
|
|
- saveMealModal.style.display = 'flex';
|
|
|
|
|
- mealNameInput.value = '';
|
|
|
|
|
- saveMealError.textContent = '';
|
|
|
|
|
- mealNameInput.focus();
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const closeSaveModal = () => {
|
|
|
|
|
- saveMealModal.style.display = 'none';
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- if (cancelSaveBtn) cancelSaveBtn.addEventListener('click', closeSaveModal);
|
|
|
|
|
-
|
|
|
|
|
- if (confirmSaveBtn) {
|
|
|
|
|
- confirmSaveBtn.addEventListener('click', async () => {
|
|
|
|
|
- const name = mealNameInput.value.trim();
|
|
|
|
|
- if (!name) {
|
|
|
|
|
- saveMealError.textContent = 'Please enter a name for your meal.';
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- confirmSaveBtn.disabled = true;
|
|
|
|
|
- confirmSaveBtn.textContent = 'Saving...';
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- const token = localStorage.getItem('localFoodToken');
|
|
|
|
|
- const response = await fetch('/api/meals', {
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- headers: {
|
|
|
|
|
- 'Content-Type': 'application/json',
|
|
|
|
|
- 'Authorization': `Bearer ${token}`
|
|
|
|
|
- },
|
|
|
|
|
- body: JSON.stringify({
|
|
|
|
|
- name: name,
|
|
|
|
|
- items: currentMealItems.map(item => ({
|
|
|
|
|
- food_id: item.id,
|
|
|
|
|
- amount_g: item.amount
|
|
|
|
|
- }))
|
|
|
|
|
- })
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (response.ok) {
|
|
|
|
|
- closeSaveModal();
|
|
|
|
|
- addMessage('system', `✅ Successfully saved "${name}" to your dashboard!`);
|
|
|
|
|
-
|
|
|
|
|
- // Trigger a refresh of the dashboard if it's open
|
|
|
|
|
- if (window.refreshDashboard) window.refreshDashboard();
|
|
|
|
|
- } else {
|
|
|
|
|
- const data = await response.json();
|
|
|
|
|
- saveMealError.textContent = data.detail || 'Failed to save meal.';
|
|
|
|
|
- }
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('Save meal error:', err);
|
|
|
|
|
- saveMealError.textContent = 'Server error. Please try again.';
|
|
|
|
|
- } finally {
|
|
|
|
|
- confirmSaveBtn.disabled = false;
|
|
|
|
|
- confirmSaveBtn.textContent = 'Save to Dashboard';
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Close modal on outside click
|
|
|
|
|
- window.addEventListener('click', (e) => {
|
|
|
|
|
- if (e.target === saveMealModal) {
|
|
|
|
|
- closeSaveModal();
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // --- Dashboard Logic (Sprint 8) ---
|
|
|
|
|
- const dashboardOverlay = document.getElementById('dashboard-overlay');
|
|
|
|
|
- const openDashboardBtn = document.getElementById('nav-dashboard-btn');
|
|
|
|
|
- const closeDashboardBtn = document.getElementById('close-dashboard-btn');
|
|
|
|
|
- const dashboardGrid = document.getElementById('dashboard-grid');
|
|
|
|
|
- const dashboardEmptyMsg = document.getElementById('dashboard-empty-msg');
|
|
|
|
|
-
|
|
|
|
|
- const toggleDashboard = (show) => {
|
|
|
|
|
- if (show) {
|
|
|
|
|
- dashboardOverlay.style.display = 'flex';
|
|
|
|
|
- loadSavedMeals();
|
|
|
|
|
- } else {
|
|
|
|
|
- dashboardOverlay.style.display = 'none';
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- if (openDashboardBtn) openDashboardBtn.addEventListener('click', () => toggleDashboard(true));
|
|
|
|
|
- if (closeDashboardBtn) closeDashboardBtn.addEventListener('click', () => toggleDashboard(false));
|
|
|
|
|
-
|
|
|
|
|
- async function loadSavedMeals() {
|
|
|
|
|
- dashboardGrid.innerHTML = '<div class="search-loading">Loading your meals...</div>';
|
|
|
|
|
- dashboardEmptyMsg.style.display = 'none';
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- const token = localStorage.getItem('localFoodToken');
|
|
|
|
|
- const response = await fetch('/api/meals', {
|
|
|
|
|
- headers: { 'Authorization': `Bearer ${token}` }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (response.ok) {
|
|
|
|
|
- const data = await response.json();
|
|
|
|
|
- renderDashboard(data.meals);
|
|
|
|
|
- } else {
|
|
|
|
|
- dashboardGrid.innerHTML = '<div class="error-text">Failed to load meals.</div>';
|
|
|
|
|
- }
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('Dashboard load error:', err);
|
|
|
|
|
- dashboardGrid.innerHTML = '<div class="error-text">Connection error.</div>';
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function renderDashboard(meals) {
|
|
|
|
|
- dashboardGrid.innerHTML = '';
|
|
|
|
|
- if (!meals || meals.length === 0) {
|
|
|
|
|
- dashboardEmptyMsg.style.display = 'block';
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- meals.forEach((meal, index) => {
|
|
|
|
|
- const card = document.createElement('div');
|
|
|
|
|
- card.className = 'meal-card';
|
|
|
|
|
- card.style.animationDelay = `${index * 0.07}s`;
|
|
|
|
|
-
|
|
|
|
|
- const dateStr = new Date(meal.created_at).toLocaleDateString(undefined, {
|
|
|
|
|
- month: 'short', day: 'numeric', year: 'numeric'
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- card.innerHTML = `
|
|
|
|
|
- <div class="meal-card-header">
|
|
|
|
|
- <span class="meal-card-name">${meal.name}</span>
|
|
|
|
|
- <span class="meal-card-date">${dateStr}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="meal-card-macros">
|
|
|
|
|
- <div class="card-macro kcal">
|
|
|
|
|
- <span class="card-macro-val">${Math.round(meal.total_calories)}</span>
|
|
|
|
|
- <span class="card-macro-lbl">kcal</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="card-macro protein">
|
|
|
|
|
- <span class="card-macro-val">${Math.round(meal.total_protein)}g</span>
|
|
|
|
|
- <span class="card-macro-lbl">Protein</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="card-macro carbs">
|
|
|
|
|
- <span class="card-macro-val">${Math.round(meal.total_carbs)}g</span>
|
|
|
|
|
- <span class="card-macro-lbl">Carbs</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="card-macro fat">
|
|
|
|
|
- <span class="card-macro-val">${Math.round(meal.total_fat)}g</span>
|
|
|
|
|
- <span class="card-macro-lbl">Fat</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="meal-card-actions">
|
|
|
|
|
- <button class="edit-meal-btn" data-id="${meal.id}" data-name="${meal.name}" title="Rename Meal">
|
|
|
|
|
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
|
|
|
|
- </button>
|
|
|
|
|
- <button class="delete-meal-btn" data-id="${meal.id}" title="Delete Meal">
|
|
|
|
|
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
- `;
|
|
|
|
|
-
|
|
|
|
|
- // Attach listeners to new buttons
|
|
|
|
|
- card.querySelector('.edit-meal-btn').addEventListener('click', (e) => {
|
|
|
|
|
- e.stopPropagation();
|
|
|
|
|
- const newName = prompt('Enter new name for this meal:', meal.name);
|
|
|
|
|
- if (newName && newName.trim() && newName !== meal.name) {
|
|
|
|
|
- renameMeal(meal.id, newName.trim());
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- card.querySelector('.delete-meal-btn').addEventListener('click', (e) => {
|
|
|
|
|
- e.stopPropagation();
|
|
|
|
|
- if (confirm(`Are you sure you want to delete "${meal.name}"?`)) {
|
|
|
|
|
- deleteMeal(meal.id);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- dashboardGrid.appendChild(card);
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async function renameMeal(id, newName) {
|
|
|
|
|
- try {
|
|
|
|
|
- const token = localStorage.getItem('localFoodToken');
|
|
|
|
|
- const response = await fetch(`/api/meals/${id}`, {
|
|
|
|
|
- method: 'PUT',
|
|
|
|
|
- headers: {
|
|
|
|
|
- 'Content-Type': 'application/json',
|
|
|
|
|
- 'Authorization': `Bearer ${token}`
|
|
|
|
|
- },
|
|
|
|
|
- body: JSON.stringify({ name: newName })
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (response.ok) {
|
|
|
|
|
- loadSavedMeals(); // Refresh
|
|
|
|
|
- } else {
|
|
|
|
|
- alert('Failed to rename meal.');
|
|
|
|
|
- }
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('Rename error:', err);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async function deleteMeal(id) {
|
|
|
|
|
- try {
|
|
|
|
|
- const token = localStorage.getItem('localFoodToken');
|
|
|
|
|
- const response = await fetch(`/api/meals/${id}`, {
|
|
|
|
|
- method: 'DELETE',
|
|
|
|
|
- headers: { 'Authorization': `Bearer ${token}` }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (response.ok) {
|
|
|
|
|
- loadSavedMeals(); // Refresh
|
|
|
|
|
- } else {
|
|
|
|
|
- alert('Failed to delete meal.');
|
|
|
|
|
- }
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('Delete error:', err);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Attach refresh helper
|
|
|
|
|
- window.refreshDashboard = loadSavedMeals;
|
|
|
|
|
});
|
|
});
|