|
41 | 41 | button.ghost{background:none; border:1px solid var(--line); color:var(--text); border-radius:12px; padding:10px 14px} |
42 | 42 | /* detail sheet */ |
43 | 43 | #sheetWrap{position:fixed; inset:0; background:rgba(0,0,0,.55); display:none; z-index:20} |
44 | | - #sheet{position:absolute; left:0; right:0; bottom:0; max-height:86%; overflow-y:auto; |
| 44 | + #sheet{position:absolute; left:0; right:0; bottom:0; max-height:86%; overflow-y:auto; padding-top:20px; |
45 | 45 | background:var(--card); border-radius:20px 20px 0 0; padding:20px 16px 30px} |
46 | 46 | #sheet h2{margin:0 0 4px; font-size:1.1rem} |
47 | 47 | .badge{display:inline-block; font-size:.72rem; background:var(--card2); color:var(--muted); |
|
57 | 57 | .chips{display:flex; flex-wrap:wrap; gap:6px} |
58 | 58 | .chip{background:var(--card2); border:1px solid var(--line); color:var(--text); border-radius:10px; padding:8px 10px; font-size:.82rem} |
59 | 59 | .chip b{color:var(--accent); font-weight:700} |
| 60 | + .chip.on{border-color:var(--accent); color:var(--accent); font-weight:600} |
| 61 | + .xclose{position:absolute; top:12px; right:12px; background:var(--card2); border:0; color:var(--muted); |
| 62 | + font-size:1.05rem; padding:6px 11px; border-radius:10px} |
60 | 63 | .entry{display:flex; align-items:center; gap:8px; background:var(--card2); border-radius:10px; padding:8px 10px; margin-top:6px; font-size:.88rem} |
61 | 64 | .entry span:first-child{flex:1} |
62 | 65 | .entry button{background:none; border:0; color:var(--muted); font-size:1rem; padding:2px 6px} |
@@ -237,8 +240,19 @@ <h1 id="titel">Training</h1> |
237 | 240 | } |
238 | 241 |
|
239 | 242 | /* ---------- helpers ---------- */ |
| 243 | +function rondeEntries(id){ |
| 244 | + const ann=annulled(); |
| 245 | + return state.log.entries.filter(x=>x.type==='ronde'&&x.oefening===id&&!ann.has(x.ts)); |
| 246 | +} |
| 247 | +function laatsteKg(id){ |
| 248 | + const ann=annulled(); |
| 249 | + const es=state.log.entries.filter(x=>!ann.has(x.ts)&&x.oefening===id&&(x.type==='gewicht'||x.type==='ronde')&&x.gewicht!=null); |
| 250 | + return es.length? es[es.length-1].gewicht : null; |
| 251 | +} |
240 | 252 | function laatsteGewicht(oefId){ |
241 | | - const e = [...state.log.entries].reverse().find(x=>x.type==='gewicht'&&x.oefening===oefId); |
| 253 | + const ann=annulled(); |
| 254 | + const es=state.log.entries.filter(x=>!ann.has(x.ts)&&x.oefening===oefId&&(x.type==='gewicht'||x.type==='ronde')&&x.gewicht!=null); |
| 255 | + const e=es[es.length-1]; |
242 | 256 | if(e) return e.gewicht+' kg ('+e.datum.slice(5)+')'; |
243 | 257 | const dag = state.schema.dagen.find(d=>(d.oefeningen||[]).some(o=>o.id===oefId&&o.vorigGewicht)); |
244 | 258 | if(dag){ const o=dag.oefeningen.find(o=>o.id===oefId); if(o.vorigGewicht) return o.vorigGewicht+' kg (vorige week)' } |
@@ -283,6 +297,7 @@ <h1 id="titel">Training</h1> |
283 | 297 | <p class="focus">${d.focus}</p> |
284 | 298 | <div class="meta">${d.duur}${d.reeks? ' · '+d.reeks:''}</div> |
285 | 299 | <div class="desc">${d.beschrijving}</div>`; |
| 300 | + if(d.rust) h+=`<div class="hist">⏱ ${d.rust}</div>`; |
286 | 301 | if((d.oefeningen||[]).length){ |
287 | 302 | h+='<ul class="exlist">'; |
288 | 303 | d.oefeningen.forEach(o=>{ |
@@ -344,24 +359,37 @@ <h1 id="titel">Training</h1> |
344 | 359 | <div class="hist">Eiwit /100 g staat op de verpakking; met gewicht erbij rekent de app zelf. Alles leeg? Dan telt het als "te schatten" tot Claude het 's nachts invult, en komt het item in je tik-lijst.</div></div>`; |
345 | 360 | main.innerHTML=h; |
346 | 361 | } |
| 362 | +function topGewichten(id, it){ |
| 363 | + const telling={}; |
| 364 | + ((it.gewichten)||[]).forEach((g,i)=>{ telling[g]=(telling[g]||0)+(0.5-i*0.1); }); // historiek (nachtelijke taak), licht gewogen |
| 365 | + const ann=annulled(); |
| 366 | + state.log.entries.filter(x=>x.type==='eiwit'&&x.item===id&&x.gram&&!ann.has(x.ts)) |
| 367 | + .forEach(x=>{ telling[x.gram]=(telling[x.gram]||0)+1; }); |
| 368 | + return Object.entries(telling).sort((a,b)=>b[1]-a[1]).slice(0,5).map(x=>parseFloat(x[0])); |
| 369 | +} |
347 | 370 | function openEiwitSheet(id){ |
348 | 371 | const it=vindItem(id); if(!it) return; |
349 | | - sheet.innerHTML=`<h2>${it.naam}</h2> |
| 372 | + const top=topGewichten(id, it); |
| 373 | + const standaard = top.length? top[0] : it.portieGram; // meest ingegeven gewicht als default |
| 374 | + let chips=''; |
| 375 | + if(top.length) chips='<div class="chips" style="margin:8px 0 0">'+top.map(g=> |
| 376 | + `<button type="button" class="chip" onclick="zetGram(${g})">${g} g</button>`).join('')+'</div>'; |
| 377 | + sheet.innerHTML=`<button class="xclose" type="button" onclick="closeSheet()">✕</button> |
| 378 | + <h2>${it.naam}</h2> |
350 | 379 | <span class="badge">${it.eiwitPer100g} g eiwit / 100 g</span> |
351 | 380 | <div class="sec">Gewicht (g)</div> |
352 | | - <div class="logrow"><input id="gIn" type="number" inputmode="numeric" value="${it.portieGram}"> |
353 | | - <button class="ghost" onclick="resetPortie(${it.portieGram})">standaard</button></div> |
| 381 | + <div class="logrow"><input id="gIn" type="number" inputmode="numeric" value="${standaard}"> |
| 382 | + <button class="ghost" type="button" onclick="zetGram(${it.portieGram})">portie</button></div> |
| 383 | + ${chips} |
354 | 384 | <div class="hist" id="gPrev"></div> |
355 | | - <br><button class="primary" onclick="bevestigEiwit('${id}')">Toevoegen</button> |
356 | | - <button class="ghost" style="width:100%;margin-top:8px" onclick="closeSheet()">Annuleren</button>`; |
| 385 | + <br><button class="primary" type="button" onclick="bevestigEiwit('${id}')">Toevoegen</button> |
| 386 | + <button class="ghost" type="button" style="width:100%;margin-top:8px" onclick="closeSheet()">Annuleren</button>`; |
357 | 387 | sheetWrap.style.display='block'; |
358 | 388 | const upd=()=>{ const g=parseFloat(document.getElementById('gIn').value)||0; |
359 | 389 | document.getElementById('gPrev').textContent='= '+Math.round(g*it.eiwitPer100g/100)+' g eiwit'; }; |
360 | 390 | document.getElementById('gIn').addEventListener('input',upd); upd(); |
361 | 391 | } |
362 | | -function resetPortie(g){ |
363 | | - const el=document.getElementById('gIn'); el.value=g; el.dispatchEvent(new Event('input')); |
364 | | -} |
| 392 | +function zetGram(g){ const el=document.getElementById('gIn'); el.value=g; el.dispatchEvent(new Event('input')); } |
365 | 393 | function bevestigEiwit(id){ |
366 | 394 | const it=vindItem(id); |
367 | 395 | const g=parseFloat(document.getElementById('gIn').value); |
@@ -445,7 +473,7 @@ <h1 id="titel">Training</h1> |
445 | 473 | h+='<div class="card"><p class="focus">Krachtprogressie</p>'; |
446 | 474 | const kr={}; |
447 | 475 | Object.entries(tr.kracht||{}).forEach(([id,v])=>{ kr[id]={naam:v.naam,punten:v.punten.slice()}; }); |
448 | | - state.log.entries.filter(x=>x.type==='gewicht'&&!ann.has(x.ts)).forEach(x=>{ |
| 476 | + state.log.entries.filter(x=>(x.type==='gewicht'||(x.type==='ronde'&&x.gewicht!=null))&&!ann.has(x.ts)).forEach(x=>{ |
449 | 477 | const naam=(state.oef[x.oefening]||{}).naam||x.oefening; |
450 | 478 | kr[x.oefening]=kr[x.oefening]||{naam,punten:[]}; |
451 | 479 | if(!kr[x.oefening].punten.some(p=>p.d===x.datum&&p.kg===x.gewicht)) kr[x.oefening].punten.push({d:x.datum,kg:x.gewicht}); |
@@ -481,35 +509,91 @@ <h1 id="titel">Training</h1> |
481 | 509 | render(); |
482 | 510 | } |
483 | 511 |
|
484 | | -/* ---------- detail sheet ---------- */ |
485 | | -let curEx=null, curDatum=null; |
| 512 | +/* ---------- detail sheet (oefening + rondes) ---------- */ |
| 513 | +let curEx=null, curDatum=null, curVariant=null; |
| 514 | +function fmtRonde(r, ex){ |
| 515 | + let s='R'+(r.ronde||'?')+': '; |
| 516 | + if(r.reps!=null) s+=r.reps+((ex.meting==='sec')?' sec':'×'); |
| 517 | + if(r.variant) s+=' '+r.variant; |
| 518 | + if(r.gewicht!=null) s+=' @'+r.gewicht+' kg'; |
| 519 | + return s; |
| 520 | +} |
| 521 | +function gekozenVariant(id){ |
| 522 | + if(curVariant) return curVariant; |
| 523 | + const rs=rondeEntries(id).filter(r=>r.variant); |
| 524 | + return rs.length? rs[rs.length-1].variant : null; |
| 525 | +} |
| 526 | +function kiesVariant(el,v){ |
| 527 | + curVariant=v; |
| 528 | + el.parentNode.querySelectorAll('.chip').forEach(c=>c.classList.remove('on')); |
| 529 | + el.classList.add('on'); |
| 530 | +} |
486 | 531 | function openSheet(id, datum){ |
487 | | - curEx=id; curDatum=datum; |
488 | | - const ex = state.oef[id]; if(!ex) return; |
489 | | - const lg = laatsteGewicht(id); |
490 | | - let h = `<h2>${ex.naam}</h2> |
| 532 | + curEx=id; curDatum=datum; curVariant=null; |
| 533 | + renderOefSheet(); |
| 534 | + sheetWrap.style.display='block'; |
| 535 | +} |
| 536 | +function renderOefSheet(){ |
| 537 | + const id=curEx, datum=curDatum; |
| 538 | + const ex=state.oef[id]; if(!ex) return; |
| 539 | + const meting=ex.meting||'reps'; |
| 540 | + const rondes=rondeEntries(id); |
| 541 | + const vandaag=rondes.filter(r=>r.datum===datum); |
| 542 | + const vDatums=[...new Set(rondes.filter(r=>r.datum<datum).map(r=>r.datum))].sort(); |
| 543 | + const vDag=vDatums[vDatums.length-1]; |
| 544 | + let h=`<button class="xclose" type="button" onclick="closeSheet()">✕</button> |
| 545 | + <h2>${ex.naam}</h2> |
491 | 546 | <span class="badge">${ex.patroon}</span><span class="badge">${ex.materiaal}</span> |
492 | 547 | ${ex.svg? '<div class="illu">'+ex.svg+'</div>':''} |
493 | 548 | <div class="sec">Uitvoering</div><div class="uitleg">${ex.uitleg}</div> |
494 | 549 | <div class="sec">Progressie</div><div class="uitleg">${ex.progressie}</div>`; |
495 | | - if(ex.logGewicht){ |
496 | | - h+=`<div class="sec">Gewicht loggen</div> |
497 | | - ${lg? '<div class="hist">Laatst: '+lg+'</div>':''} |
| 550 | + if(meting!=='geen'){ |
| 551 | + if(vDag){ |
| 552 | + const vorige=rondes.filter(r=>r.datum===vDag); |
| 553 | + h+=`<div class="sec">Vorige keer (${vDag.slice(8)}/${vDag.slice(5,7)})</div><div class="hist">${vorige.map(r=>fmtRonde(r,ex)).join(' · ')}</div>`; |
| 554 | + } else { |
| 555 | + const lg=laatsteGewicht(id); |
| 556 | + if(lg) h+=`<div class="sec">Vorige keer</div><div class="hist">${lg}</div>`; |
| 557 | + } |
| 558 | + if(vandaag.length){ |
| 559 | + h+='<div class="sec">Vandaag</div>'; |
| 560 | + vandaag.forEach(r=>{ h+=`<div class="entry"><span>${fmtRonde(r,ex)}</span><button type="button" onclick="annuleerRonde('${r.ts}')">✕</button></div>`; }); |
| 561 | + } |
| 562 | + const nr=vandaag.length+1; |
| 563 | + h+=`<div class="sec">Ronde ${nr} loggen</div> |
498 | 564 | <div class="logrow"> |
499 | | - <input id="wIn" type="number" inputmode="decimal" step="0.5" placeholder="kg"> |
500 | | - <button class="ghost" onclick="logGewicht()">Opslaan</button> |
501 | | - </div>`; |
| 565 | + <input id="rIn" type="number" inputmode="numeric" placeholder="${meting==='sec'?'seconden':'aantal herhalingen'}">`; |
| 566 | + if(ex.logGewicht){ |
| 567 | + const kg=laatsteKg(id); |
| 568 | + h+=`<input id="rKg" type="number" inputmode="decimal" step="0.5" placeholder="kg"${kg? ' value="'+kg+'"':''}>`; |
| 569 | + } |
| 570 | + h+='</div>'; |
| 571 | + if(ex.varianten && ex.varianten.length){ |
| 572 | + const gv=gekozenVariant(id); |
| 573 | + h+='<div class="chips" style="margin-top:8px">'+ex.varianten.map(v=> |
| 574 | + `<button type="button" class="chip${v===gv?' on':''}" onclick="kiesVariant(this,'${v}')">${v}</button>`).join('')+'</div>'; |
| 575 | + } |
| 576 | + h+=`<button class="primary" type="button" onclick="logRonde()">Ronde ${nr} opslaan</button> |
| 577 | + <div class="hist">⏱ Rust 60–90 sec voor je aan de volgende ronde begint.</div>`; |
502 | 578 | } |
503 | | - h+='<br><button class="primary" onclick="closeSheet()">Sluiten</button>'; |
504 | | - sheet.innerHTML=h; sheetWrap.style.display='block'; |
| 579 | + h+='<button class="ghost" type="button" style="width:100%;margin-top:10px" onclick="closeSheet()">Sluiten</button>'; |
| 580 | + sheet.innerHTML=h; |
505 | 581 | } |
506 | | -function closeSheet(){ sheetWrap.style.display='none' } |
507 | | -function logGewicht(){ |
508 | | - const v = parseFloat(document.getElementById('wIn').value); |
509 | | - if(!v){ toast('Geef een gewicht in'); return } |
510 | | - addEntry({type:'gewicht', datum:curDatum, oefening:curEx, gewicht:v}); |
511 | | - closeSheet(); render(); |
| 582 | +function logRonde(){ |
| 583 | + const ex=state.oef[curEx]; const meting=ex.meting||'reps'; |
| 584 | + const n=parseFloat(document.getElementById('rIn').value); |
| 585 | + if(isNaN(n)||n<=0){ toast(meting==='sec'?'Geef het aantal seconden in':'Geef het aantal herhalingen in'); return } |
| 586 | + const entry={type:'ronde', datum:curDatum, oefening:curEx, |
| 587 | + ronde:rondeEntries(curEx).filter(r=>r.datum===curDatum).length+1, reps:n}; |
| 588 | + const kgEl=document.getElementById('rKg'); |
| 589 | + if(kgEl){ const kg=parseFloat(kgEl.value); if(!isNaN(kg)&&kg>0) entry.gewicht=kg; } |
| 590 | + const v=gekozenVariant(curEx); |
| 591 | + if(ex.varianten && v) entry.variant=v; |
| 592 | + addEntry(entry); |
| 593 | + renderOefSheet(); render(); |
512 | 594 | } |
| 595 | +function annuleerRonde(ts){ addEntry({type:'annuleer', datum:curDatum, ref:ts}); renderOefSheet(); render(); } |
| 596 | +function closeSheet(){ sheetWrap.style.display='none' } |
513 | 597 |
|
514 | 598 | /* ---------- toast ---------- */ |
515 | 599 | let toastT; |
|
0 commit comments