'use client' import { useEffect, useState, useCallback } from 'react' import { useRouter } from 'next/navigation' import { format, isToday, isTomorrow } from 'date-fns' import { toZonedTime } from 'date-fns-tz' import { Phone, MapPin, Clock, ChevronRight, Pill, Calendar, Plus, AlertTriangle, ClipboardCheck } from 'lucide-react' import { useLiveQuery } from 'dexie-react-hooks' import { db, logDose, undoDose } from '@/lib/sync' import { calculateAllMedicationsDue, formatTimeUntil } from '@/lib/schedule' import type { Medication, DoseLog, MedicationDueStatus } from '@/lib/schedule' import { Card, CardTitle, Button, LoadingState, EmptyState, showUndoToast, showToast } from '@/components/ui' import { Header, PageContainer } from '@/components/layout/header' import { RefillAlert } from '@/components/medications/RefillAlert' import { useApp } from '../provider' const TIMEZONE = 'Australia/Perth' export default function TodayPage() { const router = useRouter() const { currentWorkspace, refreshData } = useApp() const [now, setNow] = useState(() => new Date()) const [quickNote, setQuickNote] = useState('') const [isAddingNote, setIsAddingNote] = useState(false) // Update time every minute useEffect(() => { const interval = setInterval(() => setNow(new Date()), 60000) return () => clearInterval(interval) }, []) // Fetch data from IndexedDB const appointments = useLiveQuery( () => db.appointments .where('workspaceId') .equals(currentWorkspace.id) .and((a) => !a.deletedAt && new Date(a.datetime) >= now) .sortBy('datetime'), [currentWorkspace.id, now] ) const medications = useLiveQuery( () => db.medications .where('workspaceId') .equals(currentWorkspace.id) .and((m) => m.active && !m.deletedAt) .toArray(), [currentWorkspace.id] ) const doseLogs = useLiveQuery( () => db.doseLogs .where('workspaceId') .equals(currentWorkspace.id) .toArray(), [currentWorkspace.id] ) // Calculate medication due statuses const [medStatuses, setMedStatuses] = useState([]) useEffect(() => { if (medications && doseLogs) { const meds = medications.map((m) => ({ ...m, scheduleData: m.scheduleData as unknown as Medication['scheduleData'], startDate: m.startDate ? new Date(m.startDate) : null, endDate: m.endDate ? new Date(m.endDate) : null, })) as Medication[] const logs = doseLogs.map((d) => ({ ...d, takenAt: new Date(d.takenAt), undoneAt: d.undoneAt ? new Date(d.undoneAt) : null, })) as DoseLog[] const statuses = calculateAllMedicationsDue(meds, now, logs) setMedStatuses(statuses) } }, [medications, doseLogs, now]) // Get next appointment const nextAppointment = appointments?.[0] // Get meds due soon (due within 2 hours or overdue) const medsDueSoon = medStatuses .filter((s) => { if (s.isOverdue) return true if (s.isPRN && s.prnAvailable) return true if (s.nextDueAt) { const minutesUntil = (s.nextDueAt.getTime() - now.getTime()) / 1000 / 60 return minutesUntil <= 120 } return false }) .slice(0, 5) const handleTakeMed = useCallback( async (status: MedicationDueStatus) => { try { const doseLog = await logDose( currentWorkspace.id, status.medication.id, { id: status.medication.id, name: status.medication.name } ) showUndoToast(`Took ${status.medication.name}`, async () => { await undoDose(doseLog) showToast('Dose undone', 'info') }) } catch { showToast('Failed to log dose', 'error') } }, [currentWorkspace.id] ) const handleAddQuickNote = async () => { if (!quickNote.trim()) return setIsAddingNote(true) try { const response = await fetch(`/api/workspaces/${currentWorkspace.id}/notes`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'GENERAL', content: quickNote.trim(), }), }) if (!response.ok) throw new Error('Failed to add note') setQuickNote('') showToast('Note added', 'success') await refreshData() } catch { showToast('Failed to add note', 'error') } finally { setIsAddingNote(false) } } const formatAppointmentDate = (datetime: string) => { const date = toZonedTime(new Date(datetime), TIMEZONE) if (isToday(date)) return `Today at ${format(date, 'h:mm a')}` if (isTomorrow(date)) return `Tomorrow at ${format(date, 'h:mm a')}` return format(date, 'EEE, MMM d \'at\' h:mm a') } if (!appointments || !medications) { return ( <>
) } return ( <>
{/* Greeting */}

{format(toZonedTime(now, TIMEZONE), 'EEEE, MMMM d')}

{/* Emergency & Call Clinic Buttons */}
{/* Emergency Info Button */} {/* Call Clinic Button */} {currentWorkspace.clinicPhone && (

Call Clinic

{currentWorkspace.clinicPhone}

)}
{/* Next Appointment */}

Next Appointment

{nextAppointment ? ( router.push(`/appointments/${nextAppointment.id}`)} >

{nextAppointment.title}

{formatAppointmentDate(nextAppointment.datetime)}

{nextAppointment.location && (

{nextAppointment.location}

)}
{nextAppointment.mapUrl && ( e.stopPropagation()} className="inline-flex items-center gap-1.5 mt-3 text-sm text-primary-600 font-medium hover:text-primary-700" > Open in Maps )}
) : (

No upcoming appointments

)}
{/* Prep Reminder for Tomorrow's Appointment */} {appointments && appointments.length > 0 && (() => { const tomorrowAppt = appointments.find((appt) => isTomorrow(toZonedTime(new Date(appt.datetime), TIMEZONE)) ) if (tomorrowAppt) { return (
router.push(`/appointments/${tomorrowAppt.id}/prep`)} >

Prepare for tomorrow

{tomorrowAppt.title} at {format(toZonedTime(new Date(tomorrowAppt.datetime), TIMEZONE), 'h:mm a')}

) } return null })()} {/* Refill Alerts */} {medications && medications.length > 0 && ( ({ id: m.id, name: m.name, pillCount: m.pillCount, refillThreshold: m.refillThreshold, }))} /> )} {/* Meds Due */}

Medications

{medsDueSoon.length > 0 ? (
{medsDueSoon.map((status) => ( handleTakeMed(status)} /> ))}
) : medications.length > 0 ? (

All caught up! No meds due soon.

) : ( router.push('/meds/new'), }} /> )}
{/* Quick Note */}

Quick Note

setQuickNote(e.target.value)} placeholder="Jot down a thought..." className="flex-1 px-3 py-2.5 border border-border rounded-button text-base focus:outline-none focus:ring-2 focus:ring-primary-200 focus:border-primary-500" onKeyDown={(e) => { if (e.key === 'Enter' && quickNote.trim()) { handleAddQuickNote() } }} />
) } interface MedicationCardProps { status: MedicationDueStatus now: Date onTake: () => void } function MedicationCard({ status, now, onTake }: MedicationCardProps) { const { medication, isOverdue, isPRN, prnAvailable, prnAvailableAt, nextDueAt } = status const getTimeLabel = () => { if (isOverdue && nextDueAt) { return formatTimeUntil(nextDueAt, now) } if (isPRN) { if (prnAvailable) { return 'Available now' } if (prnAvailableAt) { return `Available ${formatTimeUntil(prnAvailableAt, now)}` } return 'As needed' } if (nextDueAt) { return formatTimeUntil(nextDueAt, now) } return '' } const canTake = !isPRN || prnAvailable return (

{medication.name}

{getTimeLabel()} {isPRN && ' • As needed'}

{medication.instructions && (

{medication.instructions}

)}
) }