limoncello

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | LICENSE

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 }