Log an event that happened "now" in one tap

Logging a quick pee/poo/meal/sleep boundary took two taps: open the note
dialog, then Save. Make a single tap on a quick-action log the event
immediately at the current time.

Non-weight quick actions now call addEvent(type, "", Date.now()) directly
and show a brief snackbar with Undo (deletes the just-added event) and Add
note (opens the edit dialog for it), so notes, photos and custom times are
never lost. Weigh-ins still open the dialog since they need a value.
addEvent now returns the created event so the snackbar can reference it.
This commit is contained in:
Alexander Heldt
2026-07-10 12:44:41 +00:00
parent 01ad078b2a
commit d267ebef86
3 changed files with 105 additions and 3 deletions
+55 -3
View File
@@ -258,7 +258,7 @@
function addEvent(type, note, at, photoId, weight) { function addEvent(type, note, at, photoId, weight) {
const events = loadAll(); const events = loadAll();
const now = Date.now(); const now = Date.now();
events.push({ const ev = {
id: uuid(), id: uuid(),
type, type,
at: Number.isFinite(at) ? at : now, at: Number.isFinite(at) ? at : now,
@@ -266,10 +266,12 @@
photoId: photoId || "", photoId: photoId || "",
weight: Number.isFinite(weight) ? weight : undefined, weight: Number.isFinite(weight) ? weight : undefined,
updatedAt: now, updatedAt: now,
}); };
events.push(ev);
saveAll(events); saveAll(events);
scheduleSync(); scheduleSync();
render(); render();
return ev;
} }
function updateEvent(id, patch) { function updateEvent(id, patch) {
@@ -1602,9 +1604,59 @@
} }
}); });
// ---------- quick-log snackbar ----------
// A single tap on a quick-action logs the event at the current instant, then
// shows a brief snackbar to undo it or add detail — so the two-tap dialog flow
// is never required for a plain pee/poo/meal/sleep boundary. Weigh-ins still
// open the dialog since they need a value.
const snackbar = document.getElementById("snackbar");
const snackbarMsg = document.getElementById("snackbar-msg");
const snackbarUndo = document.getElementById("snackbar-undo");
const snackbarNote = document.getElementById("snackbar-note");
let snackbarEvent = null;
let snackbarTimer = null;
function hideSnackbar() {
clearTimeout(snackbarTimer);
snackbarTimer = null;
snackbarEvent = null;
snackbar.classList.remove("show");
snackbar.hidden = true;
}
function showSnackbar(msg, ev) {
snackbarEvent = ev;
snackbarMsg.textContent = msg;
snackbar.hidden = false;
void snackbar.offsetWidth; // reflow so the fade-in transition runs
snackbar.classList.add("show");
clearTimeout(snackbarTimer);
snackbarTimer = setTimeout(hideSnackbar, 5000);
}
function quickLog(type) {
const ev = addEvent(type, "", Date.now());
showSnackbar(`${EVENT_LABELS[type]} logged`, ev);
}
snackbarUndo.addEventListener("click", () => {
if (snackbarEvent) deleteEvent(snackbarEvent.id);
hideSnackbar();
});
snackbarNote.addEventListener("click", () => {
const ev = snackbarEvent;
hideSnackbar();
if (ev) openEditDialog(ev);
});
// ---------- wiring ---------- // ---------- wiring ----------
document.querySelectorAll("button.action").forEach(btn => { document.querySelectorAll("button.action").forEach(btn => {
btn.addEventListener("click", () => openNoteDialog(btn.dataset.type)); btn.addEventListener("click", () => {
const type = btn.dataset.type;
// Weigh-ins need a typed value, so they keep the full dialog.
if (type === "weight") { openNoteDialog(type); return; }
quickLog(type);
});
}); });
dayPicker.value = ymd(new Date()); dayPicker.value = ymd(new Date());
+7
View File
@@ -303,6 +303,13 @@
<img id="lightbox-img" alt="" /> <img id="lightbox-img" alt="" />
</dialog> </dialog>
<!-- Brief confirmation after a one-tap quick log, with Undo / Add note. -->
<div id="snackbar" class="snackbar" hidden role="status" aria-live="polite">
<span id="snackbar-msg" class="snackbar-msg"></span>
<button type="button" id="snackbar-note" class="snackbar-action">Add note</button>
<button type="button" id="snackbar-undo" class="snackbar-action">Undo</button>
</div>
<script src="app.js"></script> <script src="app.js"></script>
</body> </body>
</html> </html>
+43
View File
@@ -696,3 +696,46 @@ input.switch:checked::after { transform: translateX(18px); }
border: 1px solid rgba(255, 255, 255, 0.6); border: 1px solid rgba(255, 255, 255, 0.6);
} }
.update-banner-btn:hover { filter: brightness(0.97); } .update-banner-btn:hover { filter: brightness(0.97); }
/* ---------- quick-log snackbar ---------- */
.snackbar {
position: fixed;
left: 50%;
bottom: calc(16px + env(safe-area-inset-bottom, 0));
transform: translate(-50%, 12px);
z-index: 60;
display: flex;
align-items: center;
gap: 8px;
max-width: calc(100% - 32px);
padding: 8px 8px 8px 16px;
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
border-radius: 999px;
box-shadow: var(--shadow);
opacity: 0;
pointer-events: none;
transition: opacity 0.18s ease, transform 0.18s ease;
}
.snackbar[hidden] { display: none; }
.snackbar.show {
opacity: 1;
transform: translate(-50%, 0);
pointer-events: auto;
}
.snackbar-msg {
font-size: 0.9rem;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.snackbar-action {
background: transparent;
color: var(--accent);
font-weight: 700;
padding: 6px 10px;
flex-shrink: 0;
}
.snackbar-action:hover { filter: none; background: var(--accent-soft); }