diff --git a/scheduler/daemon.py b/scheduler/daemon.py index 5bfd13f..b87119b 100644 --- a/scheduler/daemon.py +++ b/scheduler/daemon.py @@ -317,6 +317,10 @@ def check_nagging(): now = _user_now_for(user_uuid) today = now.date() + # Skip nagging if medication is not due today + if not _is_med_due_today(med, today): + continue + # Get today's schedules try: schedules = postgres.select( @@ -333,8 +337,10 @@ def check_nagging(): # Table may not exist yet continue - # If no schedules exist, try to create them + # If no schedules exist, try to create them — but only if med is due today if not schedules: + if not _is_med_due_today(med, today): + continue logger.info(f"No schedules found for medication {med_id}, attempting to create") times = med.get("times", []) if times: diff --git a/synculous-client/src/app/dashboard/medications/[id]/edit/page.tsx b/synculous-client/src/app/dashboard/medications/[id]/edit/page.tsx new file mode 100644 index 0000000..8c1f156 --- /dev/null +++ b/synculous-client/src/app/dashboard/medications/[id]/edit/page.tsx @@ -0,0 +1,262 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter, useParams } from 'next/navigation'; +import api from '@/lib/api'; +import { ArrowLeftIcon } from '@/components/ui/Icons'; + +const DAY_OPTIONS = [ + { value: 'mon', label: 'Mon' }, + { value: 'tue', label: 'Tue' }, + { value: 'wed', label: 'Wed' }, + { value: 'thu', label: 'Thu' }, + { value: 'fri', label: 'Fri' }, + { value: 'sat', label: 'Sat' }, + { value: 'sun', label: 'Sun' }, +]; + +export default function EditMedicationPage() { + const router = useRouter(); + const params = useParams(); + const medId = params.id as string; + + const [isLoadingMed, setIsLoadingMed] = useState(true); + const [name, setName] = useState(''); + const [dosage, setDosage] = useState(''); + const [unit, setUnit] = useState('mg'); + const [frequency, setFrequency] = useState('daily'); + const [times, setTimes] = useState(['08:00']); + const [daysOfWeek, setDaysOfWeek] = useState([]); + const [intervalDays, setIntervalDays] = useState(7); + const [startDate, setStartDate] = useState(new Date().toISOString().slice(0, 10)); + const [notes, setNotes] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(''); + + useEffect(() => { + api.medications.get(medId) + .then(med => { + setName(med.name); + setDosage(String(med.dosage)); + setUnit(med.unit); + setFrequency((med as any).frequency || 'daily'); + setTimes((med as any).times?.length ? (med as any).times : ['08:00']); + setDaysOfWeek((med as any).days_of_week || []); + setIntervalDays((med as any).interval_days || 7); + setStartDate((med as any).start_date?.slice(0, 10) || new Date().toISOString().slice(0, 10)); + setNotes(med.notes || ''); + }) + .catch(() => setError('Failed to load medication.')) + .finally(() => setIsLoadingMed(false)); + }, [medId]); + + const handleAddTime = () => setTimes([...times, '12:00']); + const handleRemoveTime = (i: number) => setTimes(times.filter((_, idx) => idx !== i)); + const handleTimeChange = (i: number, val: string) => { + const t = [...times]; t[i] = val; setTimes(t); + }; + const toggleDay = (day: string) => + setDaysOfWeek(prev => prev.includes(day) ? prev.filter(d => d !== day) : [...prev, day]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!name.trim() || !dosage.trim()) { setError('Name and dosage are required.'); return; } + if (frequency === 'specific_days' && daysOfWeek.length === 0) { setError('Select at least one day.'); return; } + + setIsSubmitting(true); + setError(''); + try { + await api.medications.update(medId, { + name: name.trim(), + dosage: dosage.trim(), + unit, + frequency, + times: frequency === 'as_needed' ? [] : times, + ...(frequency === 'specific_days' && { days_of_week: daysOfWeek }), + ...(frequency === 'every_n_days' && { interval_days: intervalDays, start_date: startDate }), + ...(notes.trim() && { notes: notes.trim() }), + }); + router.push('/dashboard/medications'); + } catch (err) { + setError((err as Error).message || 'Failed to save changes.'); + setIsSubmitting(false); + } + }; + + if (isLoadingMed) { + return ( +
+
+
+ ); + } + + return ( +
+
+
+ +

Edit Medication

+
+
+ +
+ {error && ( +
+ {error} +
+ )} + +
+
+ + setName(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-indigo-500 outline-none" + /> +
+ +
+
+ + setDosage(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-indigo-500 outline-none" + /> +
+
+ + +
+
+ +
+ + +
+ + {frequency === 'specific_days' && ( +
+ +
+ {DAY_OPTIONS.map(({ value, label }) => ( + + ))} +
+
+ )} + + {frequency === 'every_n_days' && ( +
+
+ + setIntervalDays(parseInt(e.target.value) || 1)} + className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-indigo-500 outline-none" + /> +
+
+ + setStartDate(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-indigo-500 outline-none" + /> +
+
+ )} + + {frequency !== 'as_needed' && ( +
+
+ + +
+
+ {times.map((time, i) => ( +
+ handleTimeChange(i, e.target.value)} + className="flex-1 px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-indigo-500 outline-none" + /> + {times.length > 1 && ( + + )} +
+ ))} +
+
+ )} + +
+ +