feat(medication): improve snooze timer handling
This commit is contained in:
73
App.tsx
73
App.tsx
@@ -65,6 +65,7 @@ import { databaseService } from './services/database';
|
||||
import { databaseSeeder } from './services/database.seeder';
|
||||
import { logger } from './services/logging';
|
||||
import { normalizeError } from './utils/error';
|
||||
import { determineDoseStatus } from './utils/doseStatus';
|
||||
|
||||
const Header: React.FC<{
|
||||
onAdd: () => void;
|
||||
@@ -377,16 +378,35 @@ const MedicationScheduleApp: React.FC<{ user: User }> = ({ user }) => {
|
||||
async (doseId: string) => {
|
||||
if (!takenDosesDoc) return;
|
||||
const newDoses = { ...takenDosesDoc.doses };
|
||||
if (newDoses[doseId]) {
|
||||
const wasTaken = Boolean(newDoses[doseId]);
|
||||
|
||||
if (wasTaken) {
|
||||
delete newDoses[doseId];
|
||||
} else {
|
||||
newDoses[doseId] = new Date().toISOString();
|
||||
}
|
||||
|
||||
const updatedDoc = await databaseService.updateTakenDoses({
|
||||
...takenDosesDoc,
|
||||
doses: newDoses,
|
||||
});
|
||||
setTakenDosesDoc(updatedDoc);
|
||||
|
||||
if (!wasTaken) {
|
||||
setSnoozedDoses(prev => {
|
||||
if (!prev[doseId]) {
|
||||
return prev;
|
||||
}
|
||||
const updated = { ...prev };
|
||||
delete updated[doseId];
|
||||
return updated;
|
||||
});
|
||||
|
||||
if (notificationTimers.current[doseId]) {
|
||||
clearTimeout(notificationTimers.current[doseId]);
|
||||
delete notificationTimers.current[doseId];
|
||||
}
|
||||
}
|
||||
},
|
||||
[takenDosesDoc]
|
||||
);
|
||||
@@ -404,13 +424,13 @@ const MedicationScheduleApp: React.FC<{ user: User }> = ({ user }) => {
|
||||
}, []);
|
||||
|
||||
const getDoseStatus = useCallback(
|
||||
(dose: Dose, doseTime: Date, now: Date): DoseStatus => {
|
||||
if (takenDoses[dose.id]) return DoseStatus.TAKEN;
|
||||
if (snoozedDoses[dose.id] && new Date(snoozedDoses[dose.id]) > now)
|
||||
return DoseStatus.SNOOZED;
|
||||
if (doseTime.getTime() < now.getTime()) return DoseStatus.MISSED;
|
||||
return DoseStatus.UPCOMING;
|
||||
},
|
||||
(dose: Dose, doseTime: Date, now: Date): DoseStatus =>
|
||||
determineDoseStatus({
|
||||
takenAt: takenDoses[dose.id],
|
||||
snoozedUntil: snoozedDoses[dose.id],
|
||||
scheduledTime: doseTime,
|
||||
now,
|
||||
}),
|
||||
[takenDoses, snoozedDoses]
|
||||
);
|
||||
|
||||
@@ -422,15 +442,20 @@ const MedicationScheduleApp: React.FC<{ user: User }> = ({ user }) => {
|
||||
const medication = medications.find(m => m._id === item.medicationId);
|
||||
if (!medication) return null;
|
||||
|
||||
const snoozeString = snoozedDoses[item.id];
|
||||
const snoozeDate = snoozeString ? new Date(snoozeString) : undefined;
|
||||
const validSnooze =
|
||||
snoozeDate && !Number.isNaN(snoozeDate.getTime())
|
||||
? snoozeDate
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...item,
|
||||
type: 'dose' as const,
|
||||
medication,
|
||||
status: getDoseStatus(item, item.scheduledTime, currentTime),
|
||||
takenAt: takenDoses[item.id],
|
||||
snoozedUntil: snoozedDoses[item.id]
|
||||
? new Date(snoozedDoses[item.id])
|
||||
: undefined,
|
||||
snoozedUntil: validSnooze,
|
||||
};
|
||||
} else {
|
||||
// It's a Custom Reminder
|
||||
@@ -469,9 +494,10 @@ const MedicationScheduleApp: React.FC<{ user: User }> = ({ user }) => {
|
||||
let timeToNotification = -1;
|
||||
let notificationBody = '';
|
||||
let notificationTitle = '';
|
||||
let targetTime: Date | null = null;
|
||||
|
||||
if (item.type === 'dose' && item.status === DoseStatus.UPCOMING) {
|
||||
timeToNotification = item.scheduledTime.getTime() - now.getTime();
|
||||
targetTime = item.snoozedUntil ?? item.scheduledTime;
|
||||
notificationTitle = 'Time for your medication!';
|
||||
notificationBody = `${item.medication.name} (${item.medication.dosage})`;
|
||||
} else if (
|
||||
@@ -479,17 +505,21 @@ const MedicationScheduleApp: React.FC<{ user: User }> = ({ user }) => {
|
||||
item.status === DoseStatus.SNOOZED &&
|
||||
item.snoozedUntil
|
||||
) {
|
||||
timeToNotification = item.snoozedUntil.getTime() - now.getTime();
|
||||
targetTime = item.snoozedUntil;
|
||||
notificationTitle = 'Snoozed Medication Reminder';
|
||||
notificationBody = `${item.medication.name} (${item.medication.dosage})`;
|
||||
} else if (item.type === 'reminder' && item.scheduledTime > now) {
|
||||
timeToNotification = item.scheduledTime.getTime() - now.getTime();
|
||||
targetTime = item.scheduledTime;
|
||||
notificationTitle = 'Reminder';
|
||||
notificationBody = item.title;
|
||||
}
|
||||
|
||||
if (targetTime) {
|
||||
timeToNotification = targetTime.getTime() - now.getTime();
|
||||
}
|
||||
|
||||
if (timeToNotification > 0) {
|
||||
activeTimers[itemId] = setTimeout(() => {
|
||||
const timerId = window.setTimeout(() => {
|
||||
new Notification(notificationTitle, {
|
||||
body: notificationBody,
|
||||
tag: itemId,
|
||||
@@ -497,16 +527,23 @@ const MedicationScheduleApp: React.FC<{ user: User }> = ({ user }) => {
|
||||
if (item.type === 'dose' && item.status === DoseStatus.SNOOZED) {
|
||||
setSnoozedDoses(prev => {
|
||||
const newSnoozed = { ...prev };
|
||||
delete newSnoozed[itemId];
|
||||
newSnoozed[itemId] = new Date().toISOString();
|
||||
return newSnoozed;
|
||||
});
|
||||
}
|
||||
delete activeTimers[itemId];
|
||||
}, timeToNotification) as unknown as number;
|
||||
}, timeToNotification);
|
||||
|
||||
activeTimers[itemId] = timerId;
|
||||
}
|
||||
});
|
||||
|
||||
return () => Object.values(activeTimers).forEach(clearTimeout);
|
||||
return () => {
|
||||
Object.entries(activeTimers).forEach(([id, timer]) => {
|
||||
clearTimeout(timer);
|
||||
delete activeTimers[id];
|
||||
});
|
||||
};
|
||||
}, [scheduleWithStatus, settings?.notificationsEnabled]);
|
||||
|
||||
const filteredSchedule = useMemo(
|
||||
|
||||
Reference in New Issue
Block a user