`; } // ─── Export canvas (for PNG) ────────────────────────────────────────────────── function ExportCanvas({scenario,distribution,foods,targets,clientName,exportDate,exportRef}) { const allFoods = CATEGORIES.flatMap(cat=>foods[cat]||[]); const dist = distribution||{}; const uLabel = f=>f.unit==="piece"?" pcs":f.unit==="scoop"?" scp":" "+f.unit.toUpperCase(); let totalP=0,totalF=0,totalC=0; const activeMeals=[]; MEALS.forEach(meal=>{ const items=[]; Object.entries(dist[meal]||{}).forEach(([foodId,amt])=>{ if(!amt) return; const food=allFoods.find(f=>f.id===foodId); if(!food) return; const m=macros(food,amt); items.push({food,amount:amt,...m}); totalP+=m.protein; totalF+=m.fat; totalC+=m.carbs; }); if(items.length) activeMeals.push({meal,items}); }); const calTotal=totalP*4+totalF*9+totalC*4; const totalItems=activeMeals.reduce((a,m)=>a+m.items.length,0); const contentH=1244-120-110-60; const rowH=Math.max(48,Math.min(72,Math.floor((contentH-activeMeals.length*48)/Math.max(totalItems,1)))); return (
{clientName||"Meal Plan"}
{scenario?.name||"Meal Plan"}
{fmtDate(exportDate)}
{[ {label:"Calories",val:calTotal,target:targets.calories,unit:"kcal",color:"#c8f135"}, {label:"Protein",val:totalP,target:targets.protein,unit:"g",color:"#4D96FF"}, {label:"Fat",val:totalF,target:targets.fat,unit:"g",color:"#FF9F43"}, {label:"Carbs",val:totalC,target:targets.carbs,unit:"g",color:"#c8f135"}, ].map(({label,val,target,unit,color})=>(
{label}
{Math.round(val)}
target {target}{unit}
))}
{activeMeals.map(({meal,items})=>{ const mCol=MEAL_COLOR[meal]; const mP=items.reduce((a,x)=>a+x.protein,0); const mF=items.reduce((a,x)=>a+x.fat,0); const mC=items.reduce((a,x)=>a+x.carbs,0); const mK=mP*4+mF*9+mC*4; return (
{meal}
{Math.round(mP)}P{" · "} {Math.round(mF)}F{" · "} {Math.round(mC)}C{" · "} {Math.round(mK)}kcal
{items.map((item,idx)=>(
{item.food.name}
{Math.round(item.protein)}P{" · "} {Math.round(item.fat)}F{" · "} {Math.round(item.carbs)}C
{item.amount}{uLabel(item.food)}
))}
); })}
TAREK ELKHAYAT COACHING @tarekelkhayat
); } function MealPlanDashboard() { const [tab,setTab] = useState("database"); const [foods,setFoods] = useState({}); const [targets,setTargets] = useState({protein:180,fat:70,carbs:250,calories:2350}); const [scenarios,setScenarios] = useState([]); const [distribution,setDistribution] = useState({}); const [loaded,setLoaded] = useState(false); const [clientName,setClientName] = useState(""); const [exportDate,setExportDate] = useState(new Date().toISOString().split("T")[0]); const [exporting,setExporting] = useState(false); const [showExport,setShowExport] = useState(false); const [exportImg,setExportImg] = useState(null); const [exportScenId,setExportScenId] = useState(null); const exportRef = useRef(null); const [showRecovery,setShowRecovery] = useState(false); const [recoveryKeys,setRecoveryKeys] = useState([]); const [recovering,setRecovering] = useState(false); const scanStorage = async () => { setRecovering(true); try { const result = await window.storage.list(); const keys = result?.keys||[]; const found=[]; for(const key of keys){ try{ const v=await window.storage.get(key); if(!v) continue; const parsed=JSON.parse(v.value); const isFoodDB=typeof parsed==="object"&&!Array.isArray(parsed)&& Object.values(parsed).some(arr=>Array.isArray(arr)&&arr.length>0&&arr[0]?.protein!==undefined); if(isFoodDB){ const count=Object.values(parsed).reduce((a,arr)=>a+(Array.isArray(arr)?arr.length:0),0); found.push({key,count,data:parsed}); } }catch{} } setRecoveryKeys(found); }catch(e){console.error(e);} setRecovering(false); }; const restoreFrom = async(data)=>{ setFoods(data); await window.storage.set(FOODS_KEY,JSON.stringify(data)); setShowRecovery(false); setRecoveryKeys([]); }; useEffect(()=>{ (async()=>{ try{ let f=await window.storage.get(FOODS_KEY); if(!f) f=await window.storage.get("mealplan_foods_v3"); if(!f) f=await window.storage.get("mealplan_foods_v2"); if(!f) f=await window.storage.get("mealplan_foods_v1"); if(f){setFoods(JSON.parse(f.value));await window.storage.set(FOODS_KEY,f.value);} else{setFoods(DEFAULT_FOODS);await window.storage.set(FOODS_KEY,JSON.stringify(DEFAULT_FOODS));} }catch{setFoods(DEFAULT_FOODS);} try{ let t=await window.storage.get(TARGETS_KEY); if(!t) t=await window.storage.get("mealplan_targets_v3"); if(t) setTargets(JSON.parse(t.value)); }catch{} try{ const s=await window.storage.get(SCEN_KEY); if(s){const parsed=JSON.parse(s.value);setScenarios(parsed.scenarios||[]);setDistribution(parsed.distribution||{});} }catch{} setLoaded(true); })(); },[]); useEffect(()=>{if(!loaded||!Object.keys(foods).length)return;window.storage.set(FOODS_KEY,JSON.stringify(foods)).catch(()=>{});},[foods,loaded]); useEffect(()=>{if(!loaded)return;window.storage.set(TARGETS_KEY,JSON.stringify(targets)).catch(()=>{});},[targets,loaded]); useEffect(()=>{ if(!loaded) return; window.storage.set(SCEN_KEY,JSON.stringify({scenarios,distribution})).catch(()=>{}); },[scenarios,distribution,loaded]); const handleExport = async(scenId)=>{ setExportScenId(scenId); setExporting(true); await new Promise(r=>setTimeout(r,100)); try{ const h2c=await loadHtml2Canvas(); await new Promise(r=>setTimeout(r,300)); const canvas=await h2c(exportRef.current,{scale:2,useCORS:true,backgroundColor:BG,width:700,height:1244,logging:false}); setExportImg(canvas.toDataURL("image/png")); setShowExport(true); }catch(e){console.error(e);} setExporting(false); }; const exportScenario = scenarios.find(s=>s.id===exportScenId); if(!loaded) return
Loading…
; const hasContent = scenarios.length>0; return (
MealPlan
{hasContent&&( <> setClientName(e.target.value)} placeholder="Client name" style={{width:160,padding:"6px 10px",fontSize:12}}/> setExportDate(e.target.value)} style={{width:140,padding:"6px 10px",fontSize:12}}/> {/* Export dropdown */}
)}
{/* Recovery modal */} {showRecovery&&(
Recover Food Database
{recovering?(
Scanning storage…
) :recoveryKeys.length===0?(
No previous databases found.
) :(
Found {recoveryKeys.length} database{recoveryKeys.length>1?"s":""}:
{recoveryKeys.map(({key,count,data})=>(
{count} foods
{key}
))}
)}
)}
{tab==="database"&&} {tab==="generator"&&}
{showExport&&exportImg&&(
Export Preview
Right-click → "Save image as…"
Meal Plan
)} {exportScenId&&( )}
); } ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(MealPlanDashboard));