app.js (3781B)
1 'use strict'; 2 3 // Period summary (weeks / months) 4 let periodMode = 'weeks'; 5 6 // Month (calendar) view 7 let monthOffset = 0; // 0 = current month, -1 = previous month, 1 = next month, ... 8 9 // Period summary 10 11 async function loadPeriods() { 12 await fetchHTML('/summary/periods?mode=' + periodMode, 'period-list'); 13 } 14 15 async function setPeriodMode(mode) { 16 periodMode = mode; 17 document.getElementById('mode-weeks').classList.toggle('active', mode === 'weeks'); 18 document.getElementById('mode-months').classList.toggle('active', mode === 'months'); 19 await loadPeriods(); 20 document.getElementById('period-list').scrollTop = 0; 21 } 22 23 // Tiles 24 25 async function fetchHTML(url, containerId) { 26 const res = await fetch(url); 27 const html = await res.text(); 28 document.getElementById(containerId).innerHTML = html; 29 } 30 31 async function refreshAllTiles() { 32 await Promise.all([ 33 loadPeriods(), 34 loadMonth(), 35 fetchHTML('/summary', 'summary'), 36 ]); 37 } 38 39 // Month (calendar) 40 41 // Fetches the full calendar grid + label for the currently selected 42 // monthOffset and swaps it in, updating the Prev/Next button state. 43 async function loadMonth() { 44 const res = await fetch('/tiles/month?offset=' + monthOffset); 45 const data = await res.json(); 46 document.getElementById('month-label').textContent = data.label; 47 document.getElementById('month-grid').innerHTML = data.grid; 48 document.getElementById('month-next-btn').disabled = data.next_disabled; 49 } 50 51 async function shiftMonth(dir) { 52 monthOffset += dir; 53 await loadMonth(); 54 } 55 56 // Days 57 58 function openDay(date) { 59 fetch('/modal?date=' + date) 60 .then(r => r.text()) 61 .then(html => { 62 document.body.insertAdjacentHTML('beforeend', html); 63 updatePreview(); 64 document.getElementById('drink-volume').addEventListener('change', updatePreview); 65 document.getElementById('drink-abv').addEventListener('input', updatePreview); 66 }); 67 } 68 69 function closeModal(e) { 70 if (e.target.id === 'day-modal') { 71 document.getElementById('day-modal').remove(); 72 } 73 } 74 75 function refreshModal(date) { 76 const modal = document.getElementById('day-modal'); 77 if (!modal) return; 78 fetch('/modal?date=' + date) 79 .then(r => r.text()) 80 .then(html => { 81 modal.remove(); 82 document.body.insertAdjacentHTML('beforeend', html); 83 updatePreview(); 84 document.getElementById('drink-volume').addEventListener('change', updatePreview); 85 document.getElementById('drink-abv').addEventListener('input', updatePreview); 86 }); 87 } 88 89 function updatePreview() { 90 const volEl = document.getElementById('drink-volume'); 91 const abvEl = document.getElementById('drink-abv'); 92 const pre = document.getElementById('abv-preview'); 93 if (!volEl || !abvEl || !pre) return; 94 const ml = parseInt(volEl.value, 10); 95 const abv = parseFloat(abvEl.value); 96 if (ml > 0 && abv > 0) { 97 const u = (ml * abv / 1000).toFixed(2); 98 pre.textContent = `= ${u} unit${u === '1.00' ? '' : 's'}`; 99 } else { 100 pre.textContent = ''; 101 } 102 } 103 104 // Drinks 105 106 function post(url, params) { 107 return fetch(url, { 108 method: 'POST', 109 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 110 body: new URLSearchParams(params).toString(), 111 }); 112 } 113 114 async function addDrink(date) { 115 const ml = document.getElementById('drink-volume').value; 116 const abv = document.getElementById('drink-abv').value; 117 if (!ml || !abv || parseFloat(abv) <= 0) return; 118 await post('/drink/add', { date, volume_ml: ml, abv }); 119 refreshModal(date); 120 refreshAllTiles(); 121 } 122 123 async function removeDrink(date, key) { 124 await post('/drink/remove', { date, key }); 125 refreshModal(date); 126 refreshAllTiles(); 127 } 128 129 async function adjustDrink(date, key, delta) { 130 await post('/drink/adjust', { date, key, delta }); 131 refreshModal(date); 132 refreshAllTiles(); 133 }