// 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="
";
r.foods.forEach(f=>html+=`- ${f.description}
`);
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="";
r.foods.forEach(f=>{
let nut={protein:0,carbs:0,fats:0,calories:0};
f.foodNutrients.forEach(n=>{
if(n.nutrientName==="Protein") nut.protein=n.value||0;
if(n.nutrientName==="Total lipid (fat)") nut.fats =n.value||0;
if(n.nutrientName==="Carbohydrate, by difference")nut.carbs=n.value||0;
if(n.nutrientName.includes("Energy")) nut.calories=n.value||0;
});
out+=`-
${f.description} –
${nut.calories} Cal, ${nut.protein}g P, ${nut.carbs}g C, ${nut.fats}g F
`;
});
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