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:
+55
-3
@@ -258,7 +258,7 @@
|
||||
function addEvent(type, note, at, photoId, weight) {
|
||||
const events = loadAll();
|
||||
const now = Date.now();
|
||||
events.push({
|
||||
const ev = {
|
||||
id: uuid(),
|
||||
type,
|
||||
at: Number.isFinite(at) ? at : now,
|
||||
@@ -266,10 +266,12 @@
|
||||
photoId: photoId || "",
|
||||
weight: Number.isFinite(weight) ? weight : undefined,
|
||||
updatedAt: now,
|
||||
});
|
||||
};
|
||||
events.push(ev);
|
||||
saveAll(events);
|
||||
scheduleSync();
|
||||
render();
|
||||
return ev;
|
||||
}
|
||||
|
||||
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 ----------
|
||||
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());
|
||||
|
||||
@@ -303,6 +303,13 @@
|
||||
<img id="lightbox-img" alt="" />
|
||||
</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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -696,3 +696,46 @@ input.switch:checked::after { transform: translateX(18px); }
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
.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); }
|
||||
|
||||
Reference in New Issue
Block a user