limoncello

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

app.js (3824B)


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