feat: add repeat daily reminder functionality with UI updates and translations

This commit is contained in:
Daniel Volz
2025-12-25 12:29:42 +01:00
parent 3d5526875c
commit 06da1d6eb8
5 changed files with 35 additions and 6 deletions
+6
View File
@@ -16,6 +16,7 @@ type TranslationKeys = {
runsOut: string;
};
footer: string;
repeatDailyNote: string;
};
// Intake reminder email
intakeReminder: {
@@ -40,6 +41,7 @@ type TranslationKeys = {
pillsLeft: string;
daysLeft: string;
pillsAt: string;
repeatDailyNote: string;
};
// Common
common: {
@@ -66,6 +68,7 @@ const translations: Record<Language, TranslationKeys> = {
runsOut: "Runs Out",
},
footer: "🤖 Automatic reminder from MedAssist-ng",
repeatDailyNote: "You are receiving this daily reminder because 'Repeat Daily' is enabled in settings.",
},
intakeReminder: {
subject: "MedAssist-ng: Medication Reminder - {medications}",
@@ -88,6 +91,7 @@ const translations: Record<Language, TranslationKeys> = {
pillsLeft: "{count} pills",
daysLeft: "{count} days left",
pillsAt: "{count} pills at {time}",
repeatDailyNote: "(Daily reminder enabled)",
},
common: {
pill: "pill",
@@ -111,6 +115,7 @@ const translations: Record<Language, TranslationKeys> = {
runsOut: "Aufgebraucht",
},
footer: "🤖 Automatische Erinnerung von MedAssist-ng",
repeatDailyNote: "Sie erhalten diese tägliche Erinnerung, weil 'Täglich wiederholen' in den Einstellungen aktiviert ist.",
},
intakeReminder: {
subject: "MedAssist-ng: Einnahme-Erinnerung - {medications}",
@@ -133,6 +138,7 @@ const translations: Record<Language, TranslationKeys> = {
pillsLeft: "{count} Tabletten",
daysLeft: "{count} Tage übrig",
pillsAt: "{count} Tabletten um {time}",
repeatDailyNote: "(Tägliche Erinnerung aktiviert)",
},
common: {
pill: "Tablette",
+9 -1
View File
@@ -149,12 +149,20 @@ export async function settingsRoutes(app: FastifyInstance) {
app.put<{ Body: SettingsBody }>("/settings", async (request, reply) => {
const body = request.body;
// Check if any stock reminders are configured
const hasEmailStock = body.emailEnabled && body.emailStockReminders && body.notificationEmail;
const hasShoutrrrStock = body.shoutrrrEnabled && body.shoutrrrStockReminders && body.shoutrrrUrl;
const hasAnyStockReminder = hasEmailStock || hasShoutrrrStock;
// Disable repeatDailyReminders if no stock reminders are configured
const repeatDailyReminders = hasAnyStockReminder ? (body.repeatDailyReminders ?? false) : false;
// Save notification settings to JSON file
saveNotificationSettings({
emailEnabled: body.emailEnabled,
notificationEmail: body.notificationEmail,
reminderDaysBefore: body.reminderDaysBefore,
repeatDailyReminders: body.repeatDailyReminders ?? false,
repeatDailyReminders,
lowStockDays: body.lowStockDays ?? 30,
normalStockDays: body.normalStockDays ?? 90,
highStockDays: body.highStockDays ?? 180,
+9 -4
View File
@@ -255,7 +255,7 @@ async function getMedicationsNeedingReminder(reminderDaysBefore: number, languag
return lowStock;
}
async function sendReminderEmail(email: string, lowStock: LowStockItem[], language: Language): Promise<{ success: boolean; error?: string }> {
async function sendReminderEmail(email: string, lowStock: LowStockItem[], language: Language, isRepeatDaily: boolean = false): Promise<{ success: boolean; error?: string }> {
const smtpHost = process.env.SMTP_HOST;
const smtpUser = process.env.SMTP_USER;
const smtpPass = process.env.SMTP_PASS;
@@ -317,6 +317,7 @@ async function sendReminderEmail(email: string, lowStock: LowStockItem[], langua
<p style="color: #9ca3af; font-size: 11px; margin: 0;">
${tr.stockReminder.footer}
</p>
${isRepeatDaily ? `<p style="color: #9ca3af; font-size: 11px; margin: 8px 0 0 0; font-style: italic;">${tr.stockReminder.repeatDailyNote}</p>` : ""}
</div>
</div>
`;
@@ -328,7 +329,7 @@ ${tr.stockReminder.description}
${lowStock.map((r) => `${r.name}: ${r.medsLeft} ${tr.common.pills}, ${r.daysLeft ?? 0} ${tr.common.days}, ${tr.stockReminder.tableHeaders.runsOut}: ${r.depletionDate ?? tr.common.soon}`).join("\n")}
---
${tr.stockReminder.footer}`;
${tr.stockReminder.footer}${isRepeatDaily ? `\n\n${tr.stockReminder.repeatDailyNote}` : ""}`;
const subjectPlural = lowStock.length === 1 ? "" : (language === "de" ? "e" : "s");
const subject = t(tr.stockReminder.subject, { count: lowStock.length, s: subjectPlural, e: subjectPlural });
@@ -427,7 +428,7 @@ async function checkAndSendReminder(logger: { info: (msg: string) => void; error
// Send email if enabled
if (emailEnabled) {
const result = await sendReminderEmail(settings.notificationEmail, medsToNotify, language);
const result = await sendReminderEmail(settings.notificationEmail, medsToNotify, language, settings.repeatDailyReminders);
emailSuccess = result.success;
if (result.success) {
logger.info(`[Reminder] Email sent successfully to ${settings.notificationEmail}`);
@@ -441,10 +442,14 @@ async function checkAndSendReminder(logger: { info: (msg: string) => void; error
const title = medsToNotify.length === 1
? tr.push.stockTitle
: t(tr.push.stockTitleMultiple, { count: medsToNotify.length });
const message = medsToNotify
let message = medsToNotify
.map((m) => `${m.name}: ${t(tr.push.pillsLeft, { count: m.medsLeft })}, ${t(tr.push.daysLeft, { count: m.daysLeft ?? 0 })}`)
.join("\n");
if (settings.repeatDailyReminders) {
message += `\n\n${tr.push.repeatDailyNote}`;
}
const result = await sendShoutrrrNotification(settings.shoutrrrUrl, title, message);
shoutrrrSuccess = result.success;
if (result.success) {