Переглянути джерело

TG-38: Design responsive nutrient breakdown cards for macronutrient display

FerRo988 2 тижнів тому
батько
коміт
a0f94c093a
3 змінених файлів з 122 додано та 1 видалено
  1. 20 0
      static/index.html
  2. 27 1
      static/script.js
  3. 75 0
      static/style.css

+ 20 - 0
static/index.html

@@ -41,6 +41,26 @@
             </div>
         </div>
         
+        <!-- Macro Dashboard UI (US-07 Task #38) -->
+        <div class="macro-dashboard" id="macro-dashboard" style="display: none;">
+            <div class="macro-card">
+                <div class="macro-label">Calories</div>
+                <div class="macro-value" id="macro-calories">-- kcal</div>
+            </div>
+            <div class="macro-card">
+                <div class="macro-label">Protein</div>
+                <div class="macro-value" id="macro-protein">-- g</div>
+            </div>
+            <div class="macro-card">
+                <div class="macro-label">Carbs</div>
+                <div class="macro-value" id="macro-carbs">-- g</div>
+            </div>
+            <div class="macro-card">
+                <div class="macro-label">Fat</div>
+                <div class="macro-value" id="macro-fat">-- g</div>
+            </div>
+        </div>
+        
         <main class="chat-container" id="chat-container">
             <div class="message system">
                 <div class="avatar">🤖</div>

+ 27 - 1
static/script.js

@@ -234,8 +234,9 @@ document.addEventListener('DOMContentLoaded', () => {
             userInput.focus();
         }, 500);
 
-        // Load persisted chat history from the server
+        // Load persisted chat history and macro targets from the server
         await loadChatHistory();
+        await loadMacroTargets();
     }
 
     async function loadChatHistory() {
@@ -270,6 +271,29 @@ document.addEventListener('DOMContentLoaded', () => {
         }
     }
 
+    async function loadMacroTargets() {
+        const token = localStorage.getItem('localFoodToken');
+        if (!token) return;
+
+        const dashboard = document.getElementById('macro-dashboard');
+        try {
+            const response = await fetch('/api/macros/targets', {
+                headers: { 'Authorization': `Bearer ${token}` }
+            });
+
+            if (response.ok) {
+                const data = await response.json();
+                document.getElementById('macro-calories').textContent = `${data.calories} kcal`;
+                document.getElementById('macro-protein').textContent  = `${data.protein_g} g`;
+                document.getElementById('macro-carbs').textContent    = `${data.carbs_g} g`;
+                document.getElementById('macro-fat').textContent      = `${data.fat_g} g`;
+                if (dashboard) dashboard.style.display = 'flex';
+            }
+        } catch (err) {
+            console.error('Failed to load macro targets:', err);
+        }
+    }
+
     async function setLoggedOutState() {
         const token = localStorage.getItem('localFoodToken');
         if (token) {
@@ -292,6 +316,8 @@ document.addEventListener('DOMContentLoaded', () => {
         localStorage.removeItem('localFoodToken');
         chatApp.style.display = 'none';
         chatApp.classList.remove('fade-in');
+        const dashboard = document.getElementById('macro-dashboard');
+        if (dashboard) dashboard.style.display = 'none';
         
         authScreen.style.display = 'block';
         setTimeout(() => {

+ 75 - 0
static/style.css

@@ -667,3 +667,78 @@ textarea::placeholder {
     }
 }
 
+/* =============================================
+   Macro Dashboard (US-07 Task #38)
+   ============================================= */
+.macro-dashboard {
+    display: flex;
+    gap: 12px;
+    padding: 12px 20px;
+    background: rgba(255, 255, 255, 0.02);
+    border-bottom: 1px solid var(--border);
+    flex-wrap: wrap;
+    animation: fadeIn 0.4s ease;
+}
+
+.macro-card {
+    flex: 1;
+    min-width: 100px;
+    background: rgba(255, 255, 255, 0.05);
+    border: 1px solid var(--border);
+    border-radius: 12px;
+    padding: 12px 16px;
+    text-align: center;
+    backdrop-filter: blur(6px);
+    -webkit-backdrop-filter: blur(6px);
+    transition: transform 0.2s ease, border-color 0.2s ease;
+}
+
+.macro-card:hover {
+    transform: translateY(-2px);
+    border-color: rgba(255, 255, 255, 0.25);
+}
+
+.macro-label {
+    font-size: 0.75rem;
+    color: var(--text-muted);
+    text-transform: uppercase;
+    letter-spacing: 1.2px;
+    margin-bottom: 6px;
+    font-weight: 600;
+}
+
+.macro-value {
+    font-size: 1.4rem;
+    font-weight: 700;
+    transition: opacity 0.3s ease;
+}
+
+/* Color-coded per macro type */
+#macro-calories .macro-value, #macro-calories { --macro-color: #facc15; }
+#macro-protein  .macro-value, #macro-protein  { --macro-color: #ef4444; }
+#macro-carbs    .macro-value, #macro-carbs    { --macro-color: #3b82f6; }
+#macro-fat      .macro-value, #macro-fat      { --macro-color: #22c55e; }
+
+#macro-calories .macro-value { color: #facc15; }
+#macro-protein  .macro-value { color: #ef4444; }
+#macro-carbs    .macro-value { color: #3b82f6; }
+#macro-fat      .macro-value { color: #22c55e; }
+
+#macro-calories { border-color: rgba(250, 204, 21,  0.2); }
+#macro-protein  { border-color: rgba(239,  68, 68,  0.2); }
+#macro-carbs    { border-color: rgba(59,  130, 246,  0.2); }
+#macro-fat      { border-color: rgba(34,  197,  94,  0.2); }
+
+@media (max-width: 768px) {
+    .macro-dashboard {
+        gap: 8px;
+        padding: 10px 12px;
+    }
+    .macro-card {
+        min-width: 70px;
+        padding: 10px 10px;
+    }
+    .macro-value {
+        font-size: 1.1rem;
+    }
+}