diff --git a/src/app.js b/src/app.js index fbc4480..9b1e0e9 100644 --- a/src/app.js +++ b/src/app.js @@ -21,6 +21,7 @@ "poo": "Poo", "weight": "Weigh-in", "training": "Training", + "note": "Note", }; // ---------- photos: IndexedDB store ---------- @@ -894,6 +895,53 @@ } } + // Cross-day log of free-text notes (vaccinations, vet visits, milestonesβ¦), + // newest first. Unlike History this ignores the day picker so the record is + // always visible regardless of which day you're viewing. + function renderNotes(events) { + const notes = events + .filter(e => e.type === "note") + .sort((a, b) => b.at - a.at); + const list = document.getElementById("notes-list"); + const empty = document.getElementById("notes-empty"); + list.innerHTML = ""; + if (notes.length === 0) { empty.hidden = false; return; } + empty.hidden = true; + + const birthday = loadConfig().birthday; + for (const ev of notes) { + const li = document.createElement("li"); + li.className = "event"; + li.dataset.type = "note"; + li.dataset.id = ev.id; + const dateStr = new Date(ev.at).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" }); + const age = formatAgeWeeks(birthday, ev.at); + li.innerHTML = ` + + ${escapeText(age ? `${dateStr} Β· ${age}` : dateStr)} + + `; + li.querySelector(".note-text").textContent = ev.note || ""; + li.addEventListener("click", () => openEditDialog(ev)); + + for (const pid of photoIdsOf(ev)) { + const img = document.createElement("img"); + img.className = "thumb"; + img.alt = "photo"; + img.loading = "lazy"; + img.dataset.photoId = pid; + img.addEventListener("click", (e) => { + e.stopPropagation(); + openLightbox(pid); + }); + li.appendChild(img); + photoSrc(pid).then(url => { if (url) img.src = url; }); + } + + list.appendChild(li); + } + } + // ---------- lightbox ---------- const lightbox = document.getElementById("lightbox"); const lightboxImg = document.getElementById("lightbox-img"); @@ -1973,6 +2021,7 @@ renderHourHeatmap(events); renderTraining(events); renderWeight(events); + renderNotes(events); renderHistory(events); } @@ -2204,11 +2253,21 @@ function openNoteDialog(type) { pendingType = type; noteInput.value = ""; - noteTimeEdited = false; - const now = Date.now(); - noteDate.value = toDateInput(now); - noteTime.value = toTimeInput(now); - noteTitle.textContent = `Log ${EVENT_LABELS[type]}`; + const isNote = type === "note"; + // A note is about a day, so default it to the day you're viewing (at the + // current clock time). The logging types default to "now"; leaving + // noteTimeEdited false lets noteDialogAt() stamp the exact instant. + let base = Date.now(); + if (isNote) { + const day = selectedDay(); + const now = new Date(); + day.setHours(now.getHours(), now.getMinutes(), 0, 0); + base = day.getTime(); + } + noteTimeEdited = isNote; + noteDate.value = toDateInput(base); + noteTime.value = toTimeInput(base); + noteTitle.textContent = isNote ? "Add note" : `Log ${EVENT_LABELS[type]}`; const isWeight = type === "weight"; const isEat = type === "eat"; noteWeightField.hidden = !isWeight; @@ -2258,6 +2317,10 @@ document.getElementById("note-save").addEventListener("click", async (e) => { e.preventDefault(); if (!pendingType) { noteDialog.close(); return; } + if (pendingType === "note" && noteInput.value.trim() === "" && notePhotos.length === 0) { + alert("Write something for the note, or add a photo."); + return; + } let weight; if (pendingType === "weight") { weight = parseFloat(noteWeight.value); @@ -3310,9 +3373,9 @@ document.querySelectorAll("button.action").forEach(btn => { btn.addEventListener("click", () => { const type = btn.dataset.type; - // Weigh-ins need a typed value and meals ask for grams, so those two - // keep the full dialog. - if (type === "weight" || type === "eat") { openNoteDialog(type); return; } + // Weigh-ins need a typed value, meals ask for grams, and a note is all + // free text β so these open the full dialog instead of one-tap logging. + if (type === "weight" || type === "eat" || type === "note") { openNoteDialog(type); return; } quickLog(type); }); }); diff --git a/src/changelog.json b/src/changelog.json index a109b2f..14cf02d 100644 --- a/src/changelog.json +++ b/src/changelog.json @@ -1,4 +1,5 @@ [ + { "date": "2026-08-02", "text": "Added free-text notes: tap π Note to jot down things that happened on a day β vaccinations, vet visits, milestones β with a date, optional photo, and any text. All your notes are collected in a new Notes section that stays visible whatever day you're viewing, so you can see at a glance when things like a tick vaccination were done" }, { "date": "2026-08-02", "text": "The Daily counts chart now has Pees / Poos / Meals checkboxes so you can focus on just the metrics you care about β untick the rest to see, say, only poos; your choice is remembered" }, { "date": "2026-08-01", "text": "Logging a pee or poo now sets off π§/π© fireworks that shoot up from the bottom of the screen β a little celebration you can switch off in Settings (and it honours a reduced-motion preference)" }, { "date": "2026-08-01", "text": "Tidied the header on long names and ages β the name now truncates instead of shoving the buttons, and the age reads as a compact \"16 wk Β· 3 mo 3 wk\"; weight-log rows are a single line again (\"Aug 1 Β· 16 wk\")" }, diff --git a/src/index.html b/src/index.html index d57e449..fe6730d 100644 --- a/src/index.html +++ b/src/index.html @@ -113,6 +113,7 @@ + @@ -261,6 +262,12 @@
No weigh-ins logged yet.
+No notes yet. Use the π Note button to jot down things like vaccinations or vet visits β they'll be listed here across every day.
+