mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
Self-hosters without an Apple Developer account rely on Web Push to the installed PWA — there is no Live Activity or lock-screen widget to drive. Make that path carry the dose lifecycle on its own: - Stable per-slot tag. The medication dose-due reminder now sets a `med:<id>:<iso-slot>` Web Push tag (medicationDoseTag), so a re-fired reminder for the same slot coalesces instead of stacking, and a later clear can target it. - Clear-on-taken. When a dose is marked taken or skipped, push a `type:"clear"` notification reusing that slot tag. The service worker closes the matching pending reminder and refreshes the badge — the PWA equivalent of ending a Live Activity. A snooze leaves the dose outstanding and clears nothing. Wired into the single + bulk intake routes alongside the existing APNs cross-device sync. - App badge. The dispatcher carries a server-authoritative outstanding- dose count; the SW reflects it via the Badging API (setAppBadge/clearAppBadge), feature-detected and a no-op where unsupported. - The SW push handler honours requireInteraction for urgent events and renotify so a re-fired reminder re-alerts. Dose-due, preventive-care, and low-stock reminders already cascade to Web Push when no APNs channel exists; tests pin that an APNs-less instance still delivers.
40 lines
1.3 KiB
TypeScript
40 lines
1.3 KiB
TypeScript
/**
|
|
* v1.18.4 — server-authoritative count of a user's still-outstanding doses
|
|
* for the current local day. Drives the PWA app badge (`navigator.setAppBadge`)
|
|
* so the installed icon mirrors how many doses still need logging — the
|
|
* free-tier equivalent of the iOS widget's pending count.
|
|
*
|
|
* Outstanding = a today-scheduled `MedicationIntakeEvent` that is neither
|
|
* taken (`takenAt == null`) nor skipped (`skipped == false`) nor swept by the
|
|
* miss cutoff (`autoMissed == false`). Pending rows are minted by
|
|
* `projectTodayIntakesAndRecompute`; this is a read-only count over the same
|
|
* window, so callers that have just projected today's rows get an accurate
|
|
* total.
|
|
*/
|
|
import { prisma } from "@/lib/db";
|
|
import { getUserTodayBounds } from "@/lib/tz/local-day";
|
|
|
|
/**
|
|
* Count the user's outstanding (pending) doses for today in their timezone.
|
|
* Best-effort: any failure returns 0 so a badge update never blocks a caller.
|
|
*/
|
|
export async function countOutstandingDosesToday(
|
|
userId: string,
|
|
tz: string,
|
|
): Promise<number> {
|
|
try {
|
|
const { start, end } = getUserTodayBounds(new Date(), tz);
|
|
return await prisma.medicationIntakeEvent.count({
|
|
where: {
|
|
userId,
|
|
scheduledFor: { gte: start, lte: end },
|
|
takenAt: null,
|
|
skipped: false,
|
|
autoMissed: false,
|
|
},
|
|
});
|
|
} catch {
|
|
return 0;
|
|
}
|
|
}
|