2 Commits d13c2d3859 ... c38c17c6b5

Autore SHA1 Messaggio Data
  FerRo988 c38c17c6b5 ref #55: Add saved_meals/meal_items schema and POST /api/meals persistence endpoint 3 giorni fa
  FerRo988 d13c2d3859 ref #55: Add saved_meals/meal_items schema and POST /api/meals persistence endpoint 3 giorni fa
3 ha cambiato i file con 0 aggiunte e 582 eliminazioni
  1. 0 20
      static/index.html
  2. 0 240
      static/script.js
  3. 0 322
      static/style.css

+ 0 - 20
static/index.html

@@ -128,26 +128,6 @@
         </footer>
     </div>
     
-    <!-- Save Meal Modal (Sprint 8) -->
-    <div id="save-meal-modal" class="modal" style="display: none;">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h3>Save Current Meal</h3>
-                <button class="close-btn" id="cancel-save-btn">&times;</button>
-            </div>
-            <div class="modal-body">
-                <p>Give this meal a name to save it to your dashboard.</p>
-                <div class="input-group">
-                    <input type="text" id="meal-name-input" placeholder="e.g., Healthy Monday Lunch" autofocus>
-                </div>
-                <div id="save-meal-error" class="error-text"></div>
-            </div>
-            <div class="modal-footer">
-                <button id="confirm-save-btn" class="primary-btn">Save to Dashboard</button>
-            </div>
-        </div>
-    </div>
-    
     <!-- Authentication Gateway -->
     <div class="auth-container" id="auth-screen">
         <div class="auth-header">

+ 0 - 240
static/script.js

@@ -516,9 +516,6 @@ document.addEventListener('DOMContentLoaded', () => {
             if (totalProEl) totalProEl.textContent = '0';
             if (totalFatEl) totalFatEl.textContent = '0';
             if (totalCarbEl) totalCarbEl.textContent = '0';
-            
-            const saveBtn = document.getElementById('save-meal-btn');
-            if (saveBtn) saveBtn.style.display = 'none';
             return;
         }
 
@@ -557,10 +554,6 @@ document.addEventListener('DOMContentLoaded', () => {
                 if (totalProEl) animateValue(totalProEl, data.macros.protein_g);
                 if (totalFatEl) animateValue(totalFatEl, data.macros.fat_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 {
                 if (totalsBanner) totalsBanner.classList.add('has-error');
             }
@@ -883,237 +876,4 @@ document.addEventListener('DOMContentLoaded', () => {
 
     // Initialize state
     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;
 });

+ 0 - 322
static/style.css

@@ -1148,325 +1148,3 @@ textarea::placeholder {
     70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
     100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
 }
-
-/* =============================================
-   Sprint 8: Saved Meals & Dashboard UI
-   ============================================= */
-
-.secondary-btn-sm {
-    background: rgba(255, 255, 255, 0.05);
-    color: var(--text-main);
-    border: 1px solid var(--border-color);
-    border-radius: 8px;
-    padding: 8px 12px;
-    font-size: 0.85rem;
-    font-weight: 500;
-    cursor: pointer;
-    transition: all 0.2s ease;
-    display: flex;
-    align-items: center;
-    gap: 6px;
-    white-space: nowrap;
-}
-
-.secondary-btn-sm:hover {
-    background: rgba(255, 255, 255, 0.1);
-    border-color: var(--text-muted);
-}
-
-.modal {
-    position: fixed;
-    top: 0; left: 0; width: 100%; height: 100%;
-    background: rgba(0, 0, 0, 0.75);
-    backdrop-filter: blur(10px);
-    -webkit-backdrop-filter: blur(10px);
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    z-index: 2000;
-}
-
-.modal-content {
-    background: #161b22;
-    border: 1px solid var(--border-color);
-    border-radius: 20px;
-    width: 90%;
-    max-width: 420px;
-    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.6);
-    animation: modalSlideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
-    overflow: hidden;
-}
-
-@keyframes modalSlideUp {
-    from { transform: translateY(30px); opacity: 0; }
-    to { transform: translateY(0); opacity: 1; }
-}
-
-.modal-header {
-    padding: 20px 24px;
-    border-bottom: 1px solid var(--border-color);
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    background: rgba(255, 255, 255, 0.02);
-}
-
-.modal-header h3 {
-    font-size: 1.15rem;
-    font-weight: 600;
-    margin: 0;
-    color: #f0f6fc;
-}
-
-.close-btn {
-    background: none;
-    border: none;
-    color: var(--text-muted);
-    font-size: 1.8rem;
-    cursor: pointer;
-    line-height: 1;
-    padding: 0;
-    transition: color 0.2s;
-}
-
-.close-btn:hover {
-    color: #ef4444;
-}
-
-.modal-body {
-    padding: 24px;
-}
-
-.modal-body p {
-    font-size: 0.95rem;
-    color: var(--text-muted);
-    line-height: 1.5;
-    margin-bottom: 16px;
-}
-
-.modal-footer {
-    padding: 20px 24px;
-    border-top: 1px solid var(--border-color);
-    display: flex;
-    gap: 12px;
-    justify-content: flex-end;
-    background: rgba(255, 255, 255, 0.02);
-}
-
-.secondary-btn {
-    background: transparent;
-    border: 1px solid var(--border-color);
-    color: var(--text-main);
-    padding: 10px 20px;
-    border-radius: 10px;
-    font-size: 0.95rem;
-    font-weight: 500;
-    cursor: pointer;
-    transition: all 0.2s;
-}
-
-.secondary-btn:hover {
-    background: rgba(255, 255, 255, 0.05);
-    border-color: var(--text-muted);
-}
-
-/* --- Dashboard Overlay --- */
-.dashboard-overlay {
-    position: absolute;
-    top: 0; left: 0; right: 0; bottom: 0;
-    background: rgba(13, 17, 23, 0.95);
-    backdrop-filter: blur(20px);
-    -webkit-backdrop-filter: blur(20px);
-    z-index: 50;
-    display: flex;
-    flex-direction: column;
-    animation: fadeIn 0.3s ease;
-}
-
-.dashboard-content {
-    height: 100%;
-    display: flex;
-    flex-direction: column;
-}
-
-.dashboard-header {
-    padding: 24px;
-    border-bottom: 1px solid var(--border-color);
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-}
-
-.dashboard-title-group {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-}
-
-.dashboard-title-group h2 {
-    font-size: 1.25rem;
-    color: #f0f6fc;
-}
-
-.dashboard-body {
-    flex: 1;
-    overflow-y: auto;
-    padding: 24px;
-}
-
-.dashboard-grid {
-    display: grid;
-    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
-    gap: 20px;
-}
-
-.empty-meal-msg {
-    grid-column: 1 / -1;
-    text-align: center;
-    padding: 60px 20px;
-    color: var(--text-muted);
-    font-size: 1.1rem;
-    border: 2px dashed var(--border-color);
-    border-radius: 20px;
-}
-
-/* --- Meal Cards (#59) --- */
-@keyframes cardSlideIn {
-    from { opacity: 0; transform: translateY(20px); }
-    to   { opacity: 1; transform: translateY(0); }
-}
-
-.meal-card {
-    background: rgba(22, 27, 34, 0.6);
-    border: 1px solid rgba(48, 54, 61, 0.8);
-    border-radius: 18px;
-    padding: 20px;
-    display: flex;
-    flex-direction: column;
-    gap: 16px;
-    backdrop-filter: blur(12px);
-    -webkit-backdrop-filter: blur(12px);
-    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
-                border-color 0.3s ease,
-                box-shadow 0.3s ease;
-    animation: cardSlideIn 0.4s ease both;
-    cursor: default;
-}
-
-.meal-card:hover {
-    transform: translateY(-6px);
-    border-color: rgba(88, 166, 255, 0.4);
-    box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.5),
-                0 0 0 1px rgba(88, 166, 255, 0.1);
-}
-
-.meal-card-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: flex-start;
-    gap: 10px;
-}
-
-.meal-card-name {
-    font-size: 1.05rem;
-    font-weight: 700;
-    color: #f0f6fc;
-    line-height: 1.3;
-    flex: 1;
-}
-
-.meal-card-date {
-    font-size: 0.72rem;
-    color: var(--text-muted);
-    white-space: nowrap;
-    padding-top: 3px;
-}
-
-.meal-card-macros {
-    display: grid;
-    grid-template-columns: repeat(2, 1fr);
-    gap: 8px;
-}
-
-.card-macro {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    padding: 10px 8px;
-    border-radius: 12px;
-    border: 1px solid transparent;
-    gap: 2px;
-}
-
-.card-macro-val {
-    font-size: 1.1rem;
-    font-weight: 700;
-    line-height: 1;
-}
-
-.card-macro-lbl {
-    font-size: 0.65rem;
-    font-weight: 500;
-    text-transform: uppercase;
-    letter-spacing: 0.05em;
-    opacity: 0.8;
-}
-
-/* Color-coded macro badges */
-.card-macro.kcal {
-    background: rgba(251, 146, 60, 0.12);
-    border-color: rgba(251, 146, 60, 0.25);
-    color: #fb923c;
-}
-.card-macro.protein {
-    background: rgba(59, 130, 246, 0.12);
-    border-color: rgba(59, 130, 246, 0.25);
-    color: #60a5fa;
-}
-.card-macro.carbs {
-    background: rgba(34, 197, 94, 0.12);
-    border-color: rgba(34, 197, 94, 0.25);
-    color: #4ade80;
-}
-.card-macro.fat {
-    background: rgba(168, 85, 247, 0.12);
-    border-color: rgba(168, 85, 247, 0.25);
-    color: #c084fc;
-}
-
-/* --- Meal Card Actions (#61) --- */
-.meal-card-actions {
-    display: flex;
-    justify-content: flex-end;
-    gap: 10px;
-    margin-top: auto;
-    padding-top: 10px;
-    border-top: 1px solid rgba(255, 255, 255, 0.05);
-}
-
-.edit-meal-btn, .delete-meal-btn {
-    background: rgba(255, 255, 255, 0.05);
-    border: 1px solid rgba(255, 255, 255, 0.1);
-    color: var(--text-muted);
-    width: 32px;
-    height: 32px;
-    border-radius: 8px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    cursor: pointer;
-    transition: all 0.2s;
-}
-
-.edit-meal-btn:hover {
-    background: rgba(59, 130, 246, 0.15);
-    border-color: rgba(59, 130, 246, 0.3);
-    color: #60a5fa;
-}
-
-.delete-meal-btn:hover {
-    background: rgba(239, 68, 68, 0.15);
-    border-color: rgba(239, 68, 68, 0.3);
-    color: #f87171;
-}