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) {
|
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());
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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); }
|
||||||
|
|||||||
Reference in New Issue
Block a user