// FILE: macro-tracker.js jQuery(document).ready(function($) { // Initial variables and state let baseCalories = 2000; let totalCalories = 0; let mealCalories = { breakfast: 0, lunch: 0, dinner: 0, snack: 0 }; let mealTotals = { protein: 0, carbs: 0, fats: 0 }; let targetMacros = { protein: 0, carbs: 0, fats: 0 }; let macroChart = null; const mealColors = { breakfast: "#ffcc80", lunch: "#80cbc4", dinner: "#90caf9", snack: "#f48fb1" }; let historyData = []; let currentHistoryPage = 0; console.log("🔥 Macro Tracker Loaded"); // // PROFILE SETUP & BASE CALORIES // function calculateBaseCalories() { let weight = parseFloat($("#current-weight").val()) || 150; let goalWeight = parseFloat($("#goal-weight").val()) || weight; let feet = parseInt($("#user-height-feet").val()) || 5; let inches = parseInt($("#user-height-inches").val()) || 8; let sex = $("#user-sex").val(); let totalInches= (feet * 12) + inches; let age = 35; baseCalories = (sex === "male") ? 66 + (6.23 * goalWeight) + (12.7 * totalInches) - (6.8 * age) : 655 + (4.35 * goalWeight) + (4.7 * totalInches) - (4.7 * age); baseCalories = Math.round(baseCalories); $(".cal-display").text(baseCalories); $("#calorie-tally-page2, #calories-remaining").text(baseCalories); } $("#profile-setup-form input, #profile-setup-form select") .on("input change", calculateBaseCalories); // // PROFILE SAVE & PAGE NAVIGATION // $("#next-page").on("click", function() { if (!macroTrackerAjax.user_email || !macroTrackerAjax.user_email.trim()) { alert("You must log in first."); return; } if ( !$("#user-name").val().trim() || !$("#current-weight").val() || !$("#goal-weight").val() || !$("#user-height-feet").val() || !$("#user-height-inches").val() ) { alert("Please fill in all required fields."); return; } let profileData = { name: $("#user-name").val().trim(), sex: $("#user-sex").val(), height_feet: $("#user-height-feet").val(), height_inches: $("#user-height-inches").val(), weight: $("#current-weight").val(), goal_weight: $("#goal-weight").val() }; $("#user-name-display").text(profileData.name); $.ajax({ type: "POST", url: macroTrackerAjax.ajaxurl, data: { action: "save_profile", user_email: macroTrackerAjax.user_email, profile_data: profileData, nonce: macroTrackerAjax.nonce }, success: function() { $("#page-1").hide(); $("#page-2").fadeIn(); }, error: function() { alert("Error saving your profile."); } }); }); $("#back-page").on("click", function() { $("#page-2").hide(); $("#page-1").fadeIn(); }); $("#next-page-3").on("click", function() { $("#page-2").hide(); $("#page-3").fadeIn(); }); $("#back-page-2").on("click", function() { $("#page-3").hide(); $("#page-2").fadeIn(); }); // // INITIALIZE MAIN MACRO PIE (Page 2) // function initializeMacroChart() { let ctx = document.getElementById("macroChart").getContext("2d"); if (macroChart) macroChart.destroy(); macroChart = new Chart(ctx, { type: "pie", data: { labels: ["Protein %","Carbs %","Fats %"], datasets: [{ data: [30,50,20], backgroundColor: ["#ff6384","#36a2eb","#ffce56"] }] }, options: { responsive: true, maintainAspectRatio: false } }); } initializeMacroChart(); // // UPDATE MACROS ON SLIDER CHANGE // function updateMacros() { let p = parseFloat($("#protein-slider").val()); let c = parseFloat($("#carbs-slider").val()); let f = parseFloat($("#fats-slider").val()); let total = p + c + f; if (total !== 100) { let adjust = (100 - total) / 2; c += adjust; f += adjust; } let protCals = Math.round(baseCalories * (p/100)); let carbCals = Math.round(baseCalories * (c/100)); let fatCals = baseCalories - protCals - carbCals; $("#protein-value").html(`${p}% (${protCals} Cal)`); $("#carbs-value").html(`${c}% (${carbCals} Cal)`); $("#fats-value").html(`${f}% (${fatCals} Cal)`); macroChart.data.datasets[0].data = [p,c,f]; macroChart.update(); } $("#protein-slider, #carbs-slider, #fats-slider") .on("input change", updateMacros); // // MACRO RINGS SETUP & UPDATE // function ringConfig(color) { return { type: "doughnut", data: { labels: ["Used","Remaining"], datasets: [{ data: [0,100], backgroundColor: [color,"#eee"], borderWidth:0 }] }, options: { cutout: "80%", responsive: false, maintainAspectRatio: false, plugins: { tooltip: { enabled: false } } } }; } const proteinRing = new Chart( document.getElementById("protein-ring").getContext("2d"), ringConfig("#ff6384") ); const carbsRing = new Chart( document.getElementById("carbs-ring").getContext("2d"), ringConfig("#36a2eb") ); const fatsRing = new Chart( document.getElementById("fats-ring").getContext("2d"), ringConfig("#ffce56") ); function updateMacroRings() { const usedP = mealTotals.protein, usedC = mealTotals.carbs, usedF = mealTotals.fats; const targetP = Math.round(baseCalories*(parseFloat($("#protein-slider").val())/100)/4); const targetC = Math.round(baseCalories*(parseFloat($("#carbs-slider").val())/100)/4); const targetF = Math.round((baseCalories*(parseFloat($("#fats-slider").val())/100))/9); targetMacros = { protein:targetP, carbs:targetC, fats:targetF }; function set(ring, used, target, id) { const pct = Math.min(100, Math.round(used/target*100)); ring.data.datasets[0].data = [pct,100-pct]; ring.update(); $("#"+id).text(`${used}g – ${pct}%`); } set(proteinRing,usedP,targetP,"protein-ring-text"); set(carbsRing,usedC,targetC,"carbs-ring-text"); set(fatsRing,usedF,targetF,"fats-ring-text"); } // // PROGRESS BAR & MINIBAR // function updateProgressBar() { totalCalories = Object.values(mealCalories).reduce((a,b)=>a+b,0); let tp = totalCalories/baseCalories*100; let bp = mealCalories.breakfast/baseCalories*100; let lp = mealCalories.lunch/baseCalories*100; let dp = mealCalories.dinner/baseCalories*100; let sp = mealCalories.snack/baseCalories*100; if (tp>100) { let s=100/tp; bp*=s;lp*=s;dp*=s;sp*=s;tp=100; } $("#breakfast-progress").css("width",`${bp}%`); $("#lunch-progress").css("width",`${lp}%`); $("#dinner-progress").css("width",`${dp}%`); $("#snack-progress").css("width",`${sp}%`); $("#progress-label").text(`${Math.round(tp)}% of daily intake`); updateMinibar(); } function updateMinibar() { $("#minibar-breakfast").css("width",$("#breakfast-progress").css("width")); $("#minibar-lunch").css("width",$("#lunch-progress").css("width")); $("#minibar-dinner").css("width",$("#dinner-progress").css("width")); $("#minibar-snack").css("width",$("#snack-progress").css("width")); } // // MEAL TOTAL CALCS // function updateMealTotal(meal) { let c=0,p=0,C=0,f=0; $(`#${meal}-tracker tbody tr`).each(function(){ c+=parseFloat($(this).find(".meal-cal").text())||0; p+=parseFloat($(this).find(".meal-protein").text())||0; C+=parseFloat($(this).find(".meal-carbs").text())||0; f+=parseFloat($(this).find(".meal-fats").text())||0; }); mealCalories[meal]=c; // update overall macro sums mealTotals.protein = parseFloat($(".meal-protein").map((i,e)=>parseFloat($(e).text())||0).get().reduce((a,b)=>a+b,0)); mealTotals.carbs = parseFloat($(".meal-carbs"). map((i,e)=>parseFloat($(e).text())||0).get().reduce((a,b)=>a+b,0)); mealTotals.fats = parseFloat($(".meal-fats"). map((i,e)=>parseFloat($(e).text())||0).get().reduce((a,b)=>a+b,0)); $(`#${meal}-total`).text(`Cal: ${c} | P: ${p}g | C: ${C}g | F: ${f}g`); } // // UNIFIED ON-MEAL-CHANGE // function onMealChange(meal){ updateMealTotal(meal); updateTopCalories(); updateProgressBar(); updateMacroRings(); updateBadgeDisplay(); updateHistoryNav(); } function updateTopCalories(){ totalCalories = Object.values(mealCalories).reduce((a,b)=>a+b,0); $("#calories-remaining").text(baseCalories - totalCalories); } // // FOOD SEARCH & AUTO-SUGGEST // $("#food-search").on("keyup", function(){ const q=$(this).val().trim(); if(q.length<2){ $("#food-suggestions").hide(); return; } const $i=$(this), off=$i.offset(); $.ajax({ method:"GET", url:"https://api.nal.usda.gov/fdc/v1/foods/search", data:{api_key:macroTrackerAjax.usda_api_key,query:q,pageSize:5}, success(r){ if(!r.foods||!r.foods.length){ $("#food-suggestions").hide(); return; } let html=""; $("#food-suggestions").html(html).css({top:off.top+$i.outerHeight(),left:off.left,width:$i.outerWidth()}).show(); }, error(){ $("#food-suggestions").hide(); } }); }); $(document).on("click",".suggestion-item",function(){ $("#food-search").val($(this).text()); $("#food-suggestions").hide(); }); $("#search-food").on("click",function(){ const q=$("#food-search").val().trim(); if(!q){ alert("Enter a food name."); return; } $.ajax({ method:"GET", url:"https://api.nal.usda.gov/fdc/v1/foods/search", data:{api_key:macroTrackerAjax.usda_api_key,query:q,pageSize:10}, success(r){ if(!r.foods||!r.foods.length){ alert("No foods found."); return; } let out=""; $("#food-results").html(out); }, error(){ alert("Error retrieving data from the USDA API."); } }); }); // // ADD / REMOVE / SERVING-SIZE HANDLERS // $(document).on("click",".add-food",function(){ let fd; try{ fd=JSON.parse($(this).attr("data-food")); } catch{ alert("Error adding food."); return; } const meal=$("#meal-dropdown").val(); if(!meal){ alert("Select a meal."); return; } $(`#${meal}-tracker tbody`).append(` ${fd.foodName} ${fd.calories} ${fd.protein} ${fd.carbs} ${fd.fats} `); onMealChange(meal); }); $(document).on("click",".remove-food",function(){ const row=$(this).closest("tr"); const meal=row.closest("table").attr("id").replace("-tracker",""); row.remove(); onMealChange(meal); }); $(document).on("input",".serving-size",function(){ const row=$(this).closest("tr"); const s=parseFloat($(this).val())||1; const meal=row.closest("table").attr("id").replace("-tracker",""); ['cal','protein','carbs','fats'].forEach(n=>{ const base=Number(row.find(`.meal-${n}`).attr(`data-base-${n}`))||0; row.find(`.meal-${n}`).text(Math.round(base*s)); }); onMealChange(meal); }); // // DYNAMIC AI RECOMMENDATIONS // $("#get-recommendations").on("click",function(){ const gaps={ protein: targetMacros.protein - mealTotals.protein, carbs: targetMacros.carbs - mealTotals.carbs, fats: targetMacros.fats - mealTotals.fats }, rec=[]; if(gaps.protein>0){ const a=["grilled chicken","turkey breast","egg whites","Greek yogurt","tofu"]; rec.push(`🔹 Lean protein: **${a[Math.floor(Math.random()*a.length)]}**.`); } else rec.push("✅ Protein goal met."); if(gaps.carbs>0){ const b=["quinoa","brown rice","sweet potato","oats","whole-wheat pasta"]; rec.push(`🔸 Complex carbs: **${b[Math.floor(Math.random()*b.length)]}**.`); } else rec.push("✅ Carbs goal met."); if(gaps.fats>0){ const c=["avocado","almonds","olive oil","chia seeds","peanut butter"]; rec.push(`🔺 Healthy fats: **${c[Math.floor(Math.random()*c.length)]}**.`); } else rec.push("✅ Fats goal met."); $("#food-recommendations").html("").show(); }); // // HISTORY NAVIGATION // function updateHistoryNav(){ const i=currentHistoryPage; $("#history-prev") .prop("disabled",i>=historyData.length-1) .attr("data-snapshot-date", historyData[i+1]?.timestamp||""); $("#history-next") .prop("disabled",i<=0) .attr("data-snapshot-date", historyData[i-1]?.timestamp||""); } function updateMiniHistoryPage(){ const $b=$("#mini-history-body").empty(); const snap=historyData[currentHistoryPage]; if(!snap?.meals) return; Object.keys(snap.meals).forEach(m=>{ snap.meals[m].forEach(item=>{ $b.append(` ${m} ${item.foodName} ${item.calories} ${item.protein} ${item.carbs} ${item.fats} ${item.servings} ${new Date(item.timestamp).toLocaleString()} `); }); }); updateHistoryNav(); } $("#history-prev").on("click",function(){ if(currentHistoryPage0){ currentHistoryPage--; updateMiniHistoryPage(); } else alert("Most recent meal."); }); // // VIEW-CHART MODAL // $(document).on("click",".view-chart",function(){ let fd=$(this).data("food"); if(typeof fd==="string") fd=JSON.parse(fd); $("#chart-modal").show(); const ctx=document.getElementById("chart-modal-canvas").getContext("2d"); if(window.modalChart) window.modalChart.destroy(); window.modalChart=new Chart(ctx,{ type:"bar", data:{ labels:["Calories","Protein","Carbs","Fats"], datasets:[{ label:fd.foodName, data:[+fd.calories,+fd.protein,+fd.carbs,+fd.fats], backgroundColor:["#ff6384","#36a2eb","#ffce56","#4caf50"] }] }, options:{ scales:{y:{beginAtZero:true}}, plugins:{ title:{display:true,text:`Nutrition for ${fd.foodName}`},legend:{display:false} }, responsive:true } }); }); $("#close-chart-modal").on("click",()=>$("#chart-modal").hide()); // // SAVE DAILY MACROS // function saveDailyMacros(){ if(!macroTrackerAjax.user_email){ alert("No user."); return; } let md={}; ["breakfast","lunch","dinner","snack"].forEach(meal=>{ const arr=[]; $(`#${meal}-tracker tbody tr`).each(function(){ arr.push({ foodName: $(this).find("td").eq(0).text(), calories: $(this).find(".meal-cal").text(), protein: $(this).find(".meal-protein").text(), carbs: $(this).find(".meal-carbs").text(), fats: $(this).find(".meal-fats").text(), servings: $(this).find(".serving-size").val() }); }); if(arr.length) md[meal]=arr; }); if(!Object.keys(md).length){ alert("No meals."); return; } $.ajax({ type:"POST", url:macroTrackerAjax.ajaxurl, data:{ action:"save_meal", user_email:macroTrackerAjax.user_email, meal_data:JSON.stringify(md), nonce:macroTrackerAjax.nonce }, success(){ alert("Meal saved!"); loadSavedUserData(); updateMinibar(); }, error(){ alert("Error saving meal."); } }); } $("#save-meal").on("click",saveDailyMacros); // // START FRESH // $("#start-fresh").on("click",function(){ if(!confirm("Clear all data?")) return; $(".meal-table tbody").empty(); mealCalories={breakfast:0,lunch:0,dinner:0,snack:0}; mealTotals={protein:0,carbs:0,fats:0}; updateTopCalories(); updateProgressBar(); $("#food-search").val(""); $("#food-suggestions").hide(); historyData=[]; $("#mini-history-body").empty(); $("#user-name").val(""); $("#user-name-display").text(""); $("#page-3,#page-2").hide(); $("#page-1").fadeIn(); }); // // PDF GENERATION // $("#download-pdf").on("click",function(){ const { jsPDF } = window.jspdf; let doc=new jsPDF(); doc.setFontSize(16); doc.text("Macro & Meal Report for "+$("#user-name-display").text(),10,20); doc.setFontSize(12); doc.text("Total Daily Calories: "+$("#calorie-tally-page2").text(),10,30); doc.text("Calories Remaining: "+$("#calories-remaining").text(),10,40); let ctx=document.getElementById("exportMacroChart").getContext("2d"); let data=JSON.parse(JSON.stringify(macroChart.data)); let exportChart=new Chart(ctx,{type:"pie",data:data,options:{responsive:false,maintainAspectRatio:false,animation:{duration:0}}}); requestAnimationFrame(function(){ try{ let img=exportChart.toBase64Image(); exportChart.destroy(); doc.addImage(img,"PNG",10,50,120,120); let y=180; doc.setFontSize(14); doc.text("Meal Breakdown:",10,y); y+=8; if(historyData.length){ historyData[0].meals && Object.keys(historyData[0].meals).forEach(meal=>{ doc.setFontSize(14); doc.text(`${meal} Meal:`,10,y); y+=8; historyData[0].meals[meal].forEach(item=>{ doc.setFontSize(12); let line=`${item.foodName} - ${item.calories} Cal, ${item.protein}g P, ${item.carbs}g C, ${item.fats}g F, Serv:${item.servings}`; doc.text(line,10,y); y+=8; if(y>280){ doc.addPage(); y=20; } }); y+=10; }); } else { doc.setFontSize(12); doc.text("No meal history.",10,y); } doc.save("MacroTracker_Report.pdf"); } catch(e){ console.error("PDF error:",e); alert("Error generating PDF."); } }); }); // // AUTH, USER STATE, HISTORY, LOAD // function bindAuthButtons(){ $("#global-logout-button").on("click",function(e){ e.preventDefault(); if(confirm("Log out?")) window.location.href=macroTrackerAjax.logout_url; }); $("#google-login").on("click",function(e){ e.preventDefault(); let url=new URL("https://accounts.google.com/o/oauth2/auth"); url.searchParams.set("client_id",macroTrackerAjax.google_client_id); url.searchParams.set("redirect_uri",macroTrackerAjax.google_redirect_uri); url.searchParams.set("scope","email profile"); url.searchParams.set("response_type","code"); url.searchParams.set("access_type","offline"); url.searchParams.set("prompt","select_account"); window.location.href=url.toString(); }); } bindAuthButtons(); function updateUserState(){ $.post(macroTrackerAjax.ajaxurl,{action:"check_user_state",nonce:macroTrackerAjax.nonce},function(resp){ if(resp.success&&resp.data.loggedin){ if(!macroTrackerAjax.user_email.trim()) macroTrackerAjax.user_email=resp.data.user_email; togglePages(true); loadSavedUserData(); } else togglePages(false); }); } updateUserState(); function togglePages(loggedIn){ if(loggedIn){ $("#page-1,#page-2").hide(); $("#page-3,#progress-bar-container,#history-panel,#get-recommendations,#food-recommendations,#minibar-container,#back-page-2").fadeIn(); } else { $("#page-3,#progress-bar-container,#history-panel,#get-recommendations,#food-recommendations,#minibar-container,#back-page-2").hide(); $("#page-1").fadeIn(); } } function loadSavedUserData(){ $.ajax({ type:"GET", url:macroTrackerAjax.ajaxurl, data:{action:"load_user_macros",user_email:macroTrackerAjax.user_email}, success(r){ if(r.success&&r.data.name){ $("#user-name").val(r.data.name); $("#user-name-display").text(r.data.name); $("#user-sex").val(r.data.sex); $("#user-height-feet").val(r.data.height_feet); $("#user-height-inches").val(r.data.height_inches); $("#current-weight").val(r.data.weight); $("#goal-weight").val(r.data.goal_weight); calculateBaseCalories(); if(r.data.history&&r.data.history.length){ historyData=r.data.history; currentHistoryPage=0; updateMiniHistoryPage(); } } if(macroTrackerAjax.user_email.trim()) togglePages(true); else togglePages(false); }, error(e){ console.error("Load error:",e); } }); } // // ADJUST FOR TABLE WIDTH MOBILE // function adjustForTable(){ const t=document.querySelector('.meal-table'); const c=document.querySelector('#macro-tracker'); if(t&&window.innerWidth<768) c.style.minWidth=t.offsetWidth+'px'; else c.style.minWidth='100%'; } window.addEventListener('load',adjustForTable); window.addEventListener('resize',adjustForTable); }); // end document.ready