Add free-text day notes with an always-visible notes log
A new 📝 Note quick-action logs a free-text note (with a date, optional
photo, and any text), defaulting to the day you're viewing. Notes are a
distinct "note" event type, so they edit, sync and delete like any other
event, and a new Notes section lists them newest-first across every day —
independent of the day picker — so records like vaccinations stay visible
whatever day you're looking at.
This commit is contained in:
+71
-8
@@ -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 = `
|
||||
<span class="dot"></span>
|
||||
<span class="note-date">${escapeText(age ? `${dateStr} · ${age}` : dateStr)}</span>
|
||||
<span class="note-text"></span>
|
||||
`;
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user