feat: enhance reminder system with notification type and channel tracking
This commit is contained in:
@@ -140,6 +140,8 @@ export async function settingsRoutes(app: FastifyInstance) {
|
||||
// Reminder state
|
||||
lastAutoEmailSent: reminderState.lastAutoEmailSent,
|
||||
nextScheduledCheck: reminderState.nextScheduledCheck,
|
||||
lastNotificationType: reminderState.lastNotificationType,
|
||||
lastNotificationChannel: reminderState.lastNotificationChannel,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { readFileSync, writeFileSync, existsSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { loadNotificationSettings, sendShoutrrrNotification } from "../routes/settings.js";
|
||||
import { getTranslations, t, getDateLocale, type Language } from "../i18n/translations.js";
|
||||
import { getReminderState, updateReminderSentTime } from "./reminder-scheduler.js";
|
||||
|
||||
type Slice = { usage: number; every: number; start: string };
|
||||
|
||||
@@ -65,10 +66,10 @@ type UpcomingIntake = {
|
||||
|
||||
function getUpcomingIntakes(medName: string, slices: Slice[], minutesBefore: number): UpcomingIntake[] {
|
||||
const now = Date.now();
|
||||
// Window looks 2 minutes into past and (minutesBefore + 1) minutes into future
|
||||
// This ensures we don't miss reminders due to timing jitter
|
||||
// Window to detect if "now" is the right time to send reminder
|
||||
// We check if the notify time (intake - 15min) falls within current minute ±1
|
||||
const windowStart = now - 2 * 60 * 1000; // 2 minutes ago (catch slightly late checks)
|
||||
const windowEnd = now + (minutesBefore + 1) * 60 * 1000; // minutesBefore + 1 minute from now
|
||||
const windowEnd = now + 1 * 60 * 1000; // 1 minute from now
|
||||
|
||||
const upcoming: UpcomingIntake[] = [];
|
||||
|
||||
@@ -312,6 +313,10 @@ async function checkAndSendIntakeReminders(logger: { info: (msg: string) => void
|
||||
saveIntakeReminderState({
|
||||
sentReminders: [...cleanedReminders, ...newKeys],
|
||||
});
|
||||
|
||||
// Update global reminder state for UI display
|
||||
const channel = emailSuccess && shoutrrrSuccess ? "both" : emailSuccess ? "email" : "push";
|
||||
updateReminderSentTime("intake", channel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ type ReminderState = {
|
||||
lastAutoEmailDate: string | null; // YYYY-MM-DD - to track if we already sent today
|
||||
notifiedMedications: string[]; // List of medication names that have been notified (cleared when restocked)
|
||||
nextScheduledCheck: string | null; // ISO date string for when the next check is scheduled
|
||||
lastNotificationType: "stock" | "intake" | null; // Type of last notification
|
||||
lastNotificationChannel: "email" | "push" | "both" | null; // Channel used for last notification
|
||||
};
|
||||
|
||||
const REMINDER_HOUR = 6; // 6:00 AM local time
|
||||
@@ -158,12 +160,14 @@ function loadReminderState(): ReminderState {
|
||||
lastAutoEmailDate: saved.lastAutoEmailDate ?? null,
|
||||
notifiedMedications: saved.notifiedMedications ?? [],
|
||||
nextScheduledCheck: saved.nextScheduledCheck ?? null,
|
||||
lastNotificationType: saved.lastNotificationType ?? null,
|
||||
lastNotificationChannel: saved.lastNotificationChannel ?? null,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return { lastAutoEmailSent: null, lastAutoEmailDate: null, notifiedMedications: [], nextScheduledCheck: null };
|
||||
return { lastAutoEmailSent: null, lastAutoEmailDate: null, notifiedMedications: [], nextScheduledCheck: null, lastNotificationType: null, lastNotificationChannel: null };
|
||||
}
|
||||
|
||||
function saveReminderState(state: ReminderState): void {
|
||||
@@ -174,13 +178,15 @@ export function getReminderState(): ReminderState {
|
||||
return loadReminderState();
|
||||
}
|
||||
|
||||
export function updateReminderSentTime(): void {
|
||||
export function updateReminderSentTime(type: "stock" | "intake" = "stock", channel: "email" | "push" | "both" = "email"): void {
|
||||
const state = loadReminderState();
|
||||
const today = getTodayInTimezone();
|
||||
saveReminderState({
|
||||
...state,
|
||||
lastAutoEmailSent: new Date().toISOString(),
|
||||
lastAutoEmailDate: today,
|
||||
lastNotificationType: type,
|
||||
lastNotificationChannel: channel,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -451,11 +457,14 @@ async function checkAndSendReminder(logger: { info: (msg: string) => void; error
|
||||
// Update state if any notification was sent successfully
|
||||
if (emailSuccess || shoutrrrSuccess) {
|
||||
const currentState = loadReminderState();
|
||||
const channel = emailSuccess && shoutrrrSuccess ? "both" : emailSuccess ? "email" : "push";
|
||||
saveReminderState({
|
||||
lastAutoEmailSent: new Date().toISOString(),
|
||||
lastAutoEmailDate: today,
|
||||
notifiedMedications: [...new Set([...stillLowStock, ...medsToNotify.map((m) => m.name)])],
|
||||
nextScheduledCheck: currentState.nextScheduledCheck,
|
||||
lastNotificationType: "stock",
|
||||
lastNotificationChannel: channel,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user