added dark mode

This commit is contained in:
2026-02-15 23:11:33 -06:00
parent e97347ff65
commit d8fde5b516
18 changed files with 543 additions and 366 deletions

View File

@@ -81,52 +81,52 @@ export default function NewMedicationPage() {
};
return (
<div className="min-h-screen bg-gray-50">
<header className="bg-white border-b border-gray-200 sticky top-0 z-10">
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
<header className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 sticky top-0 z-10">
<div className="flex items-center gap-3 px-4 py-3">
<button onClick={() => router.back()} className="p-1">
<button onClick={() => router.back()} className="p-1 text-gray-600 dark:text-gray-400">
<ArrowLeftIcon size={24} />
</button>
<h1 className="text-xl font-bold text-gray-900">Add Medication</h1>
<h1 className="text-xl font-bold text-gray-900 dark:text-gray-100">Add Medication</h1>
</div>
</header>
<form onSubmit={handleSubmit} className="p-4 space-y-6">
{error && (
<div className="bg-red-50 text-red-600 px-4 py-3 rounded-lg text-sm">
<div className="bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400 px-4 py-3 rounded-lg text-sm">
{error}
</div>
)}
<div className="bg-white rounded-xl p-4 shadow-sm space-y-4">
<div className="bg-white dark:bg-gray-800 rounded-xl p-4 shadow-sm space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Medication Name</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Medication Name</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="e.g., Vitamin D"
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none"
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 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-indigo-500 outline-none"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Dosage</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Dosage</label>
<input
type="text"
value={dosage}
onChange={(e) => setDosage(e.target.value)}
placeholder="e.g., 1000"
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none"
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 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-indigo-500 outline-none"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Unit</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Unit</label>
<select
value={unit}
onChange={(e) => setUnit(e.target.value)}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none"
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"
>
<option value="mg">mg</option>
<option value="mcg">mcg</option>
@@ -140,11 +140,11 @@ export default function NewMedicationPage() {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Frequency</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Frequency</label>
<select
value={frequency}
onChange={(e) => setFrequency(e.target.value)}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none"
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"
>
<option value="daily">Daily</option>
<option value="specific_days">Specific Days of Week</option>
@@ -156,7 +156,7 @@ export default function NewMedicationPage() {
{/* Day-of-week picker for specific_days */}
{frequency === 'specific_days' && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Days</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Days</label>
<div className="flex gap-2 flex-wrap">
{DAY_OPTIONS.map(({ value, label }) => (
<button
@@ -166,7 +166,7 @@ export default function NewMedicationPage() {
className={`px-3 py-2 rounded-lg text-sm font-medium border transition-colors ${
daysOfWeek.includes(value)
? 'bg-indigo-600 text-white border-indigo-600'
: 'bg-white text-gray-700 border-gray-300'
: 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600'
}`}
>
{label}
@@ -180,22 +180,22 @@ export default function NewMedicationPage() {
{frequency === 'every_n_days' && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Every N Days</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Every N Days</label>
<input
type="number"
min={1}
value={intervalDays}
onChange={(e) => setIntervalDays(parseInt(e.target.value) || 1)}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none"
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 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-indigo-500 outline-none"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Starting From</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Starting From</label>
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none"
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"
/>
</div>
</div>
@@ -205,17 +205,17 @@ export default function NewMedicationPage() {
{frequency !== 'as_needed' && (
<div>
<div className="flex items-center justify-between mb-1">
<label className="block text-sm font-medium text-gray-700">Times</label>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Times</label>
<button
type="button"
onClick={handleAddTime}
className="text-indigo-600 text-sm font-medium"
className="text-indigo-600 dark:text-indigo-400 text-sm font-medium"
>
+ Add Time
</button>
</div>
{frequency === 'daily' && (
<p className="text-xs text-gray-400 mb-2">Add multiple times for 2x, 3x, or more doses per day</p>
<p className="text-xs text-gray-400 dark:text-gray-500 mb-2">Add multiple times for 2x, 3x, or more doses per day</p>
)}
<div className="space-y-2">
{times.map((time, index) => (
@@ -224,13 +224,13 @@ export default function NewMedicationPage() {
type="time"
value={time}
onChange={(e) => handleTimeChange(index, e.target.value)}
className="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none"
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 && (
<button
type="button"
onClick={() => handleRemoveTime(index)}
className="text-red-500 px-3"
className="text-red-500 dark:text-red-400 px-3"
>
Remove
</button>

View File

@@ -214,7 +214,7 @@ export default function MedicationsPage() {
return (
<div className="p-4 space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-900">Medications</h1>
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">Medications</h1>
<Link href="/dashboard/medications/new" className="bg-indigo-600 text-white p-2 rounded-full">
<PlusIcon size={24} />
</Link>
@@ -224,7 +224,7 @@ export default function MedicationsPage() {
<PushNotificationToggle />
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg">
<div className="bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-400 px-4 py-3 rounded-lg">
{error}
</div>
)}
@@ -232,38 +232,38 @@ export default function MedicationsPage() {
{/* Due Now Section */}
{dueEntries.length > 0 && (
<div>
<h2 className="text-lg font-semibold text-gray-900 mb-3">Due</h2>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3">Due</h2>
<div className="space-y-3">
{dueEntries.map((entry) => (
<div
key={`${entry.item.medication.id}-${entry.time}`}
className={`bg-white rounded-xl p-4 shadow-sm ${borderColor(entry.status)}`}
className={`bg-white dark:bg-gray-800 rounded-xl p-4 shadow-sm ${borderColor(entry.status)}`}
>
<div className="flex items-center justify-between mb-2">
<div>
<div className="flex items-center gap-2">
<h3 className="font-semibold text-gray-900">{entry.item.medication.name}</h3>
<h3 className="font-semibold text-gray-900 dark:text-gray-100">{entry.item.medication.name}</h3>
{entry.item.is_previous_day && (
<span className="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded">Yesterday</span>
<span className="text-xs bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-400 px-2 py-0.5 rounded">Yesterday</span>
)}
</div>
<p className="text-sm text-gray-500">{entry.item.medication.dosage} {entry.item.medication.unit}</p>
<p className="text-sm text-gray-500 dark:text-gray-400">{entry.item.medication.dosage} {entry.item.medication.unit}</p>
</div>
</div>
<div className="flex items-center justify-between bg-gray-50 rounded-lg p-3">
<div className="flex items-center justify-between bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
<div className="flex items-center gap-2">
<ClockIcon size={16} className="text-gray-500" />
<span className="font-medium">{entry.time}</span>
<ClockIcon size={16} className="text-gray-500 dark:text-gray-400" />
<span className="font-medium text-gray-900 dark:text-gray-100">{entry.time}</span>
{entry.status === 'overdue' && (
<span className="text-xs text-red-600 font-medium">Overdue</span>
<span className="text-xs text-red-600 dark:text-red-400 font-medium">Overdue</span>
)}
</div>
{entry.status === 'taken' ? (
<span className="text-green-600 font-medium flex items-center gap-1">
<span className="text-green-600 dark:text-green-400 font-medium flex items-center gap-1">
<CheckIcon size={16} /> Taken
</span>
) : entry.status === 'skipped' ? (
<span className="text-gray-400 font-medium">Skipped</span>
<span className="text-gray-400 dark:text-gray-500 font-medium">Skipped</span>
) : (
<div className="flex gap-2">
<button
@@ -274,7 +274,7 @@ export default function MedicationsPage() {
</button>
<button
onClick={() => handleSkip(entry.item.medication.id, entry.time)}
className="text-gray-500 px-2 py-1"
className="text-gray-500 dark:text-gray-400 px-2 py-1"
>
Skip
</button>
@@ -290,18 +290,18 @@ export default function MedicationsPage() {
{/* PRN Section */}
{prnEntries.length > 0 && (
<div>
<h2 className="text-lg font-semibold text-gray-900 mb-3">As Needed</h2>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3">As Needed</h2>
<div className="space-y-3">
{prnEntries.map((item) => (
<div key={item.medication.id} className="bg-white rounded-xl p-4 shadow-sm">
<div key={item.medication.id} className="bg-white dark:bg-gray-800 rounded-xl p-4 shadow-sm">
<div className="flex items-center justify-between mb-2">
<div>
<h3 className="font-semibold text-gray-900">{item.medication.name}</h3>
<p className="text-sm text-gray-500">{item.medication.dosage} {item.medication.unit}</p>
<h3 className="font-semibold text-gray-900 dark:text-gray-100">{item.medication.name}</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">{item.medication.dosage} {item.medication.unit}</p>
</div>
</div>
<div className="flex items-center justify-between bg-gray-50 rounded-lg p-3">
<span className="text-gray-500 text-sm">As needed</span>
<div className="flex items-center justify-between bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
<span className="text-gray-500 dark:text-gray-400 text-sm">As needed</span>
<button
onClick={() => handleTake(item.medication.id)}
className="bg-green-600 text-white px-3 py-1 rounded-lg text-sm font-medium"
@@ -318,24 +318,24 @@ export default function MedicationsPage() {
{/* Upcoming Section */}
{upcomingEntries.length > 0 && (
<div>
<h2 className="text-lg font-semibold text-gray-500 mb-3">Upcoming</h2>
<h2 className="text-lg font-semibold text-gray-500 dark:text-gray-400 mb-3">Upcoming</h2>
<div className="space-y-3">
{upcomingEntries.map((entry) => (
<div
key={`${entry.item.medication.id}-${entry.time}`}
className="bg-gray-50 rounded-xl p-4 shadow-sm opacity-75"
className="bg-gray-50 dark:bg-gray-800 rounded-xl p-4 shadow-sm opacity-75"
>
<div className="flex items-center justify-between">
<div>
<div className="flex items-center gap-2">
<h3 className="font-medium text-gray-700">{entry.item.medication.name}</h3>
<h3 className="font-medium text-gray-700 dark:text-gray-300">{entry.item.medication.name}</h3>
{entry.item.is_next_day && (
<span className="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded">Tomorrow</span>
<span className="text-xs bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 px-2 py-0.5 rounded">Tomorrow</span>
)}
</div>
<p className="text-sm text-gray-400">{entry.item.medication.dosage} {entry.item.medication.unit}</p>
<p className="text-sm text-gray-400 dark:text-gray-500">{entry.item.medication.dosage} {entry.item.medication.unit}</p>
</div>
<div className="flex items-center gap-2 text-gray-400">
<div className="flex items-center gap-2 text-gray-400 dark:text-gray-500">
<ClockIcon size={16} />
<span className="font-medium">{entry.time}</span>
</div>
@@ -348,15 +348,15 @@ export default function MedicationsPage() {
{/* All Medications */}
<div>
<h2 className="text-lg font-semibold text-gray-900 mb-3">All Medications</h2>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3">All Medications</h2>
{medications.length === 0 ? (
<div className="bg-white rounded-xl p-8 shadow-sm text-center">
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<PillIcon className="text-gray-400" size={32} />
<div className="bg-white dark:bg-gray-800 rounded-xl p-8 shadow-sm text-center">
<div className="w-16 h-16 bg-gray-100 dark:bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-4">
<PillIcon className="text-gray-400 dark:text-gray-500" size={32} />
</div>
<h3 className="font-semibold text-gray-900 mb-1">No medications yet</h3>
<p className="text-gray-500 text-sm mb-4">Add your medications to track them</p>
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">No medications yet</h3>
<p className="text-gray-500 dark:text-gray-400 text-sm mb-4">Add your medications to track them</p>
<Link href="/dashboard/medications/new" className="inline-block bg-indigo-600 text-white px-4 py-2 rounded-lg font-medium">
Add Medication
</Link>
@@ -366,41 +366,41 @@ export default function MedicationsPage() {
{medications.map((med) => {
const { percent: adherencePercent, isPrn } = getAdherenceForMed(med.id);
return (
<div key={med.id} className="bg-white rounded-xl p-4 shadow-sm">
<div key={med.id} className="bg-white dark:bg-gray-800 rounded-xl p-4 shadow-sm">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2">
<h3 className="font-semibold text-gray-900">{med.name}</h3>
<h3 className="font-semibold text-gray-900 dark:text-gray-100">{med.name}</h3>
{!med.active && (
<span className="text-xs bg-gray-100 text-gray-500 px-2 py-0.5 rounded">Inactive</span>
<span className="text-xs bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 px-2 py-0.5 rounded">Inactive</span>
)}
</div>
<p className="text-gray-500 text-sm">{med.dosage} {med.unit} &middot; {formatSchedule(med)}</p>
<p className="text-gray-500 dark:text-gray-400 text-sm">{med.dosage} {med.unit} &middot; {formatSchedule(med)}</p>
{med.times.length > 0 && (
<p className="text-gray-400 text-sm mt-1">Times: {med.times.join(', ')}</p>
<p className="text-gray-400 dark:text-gray-500 text-sm mt-1">Times: {med.times.join(', ')}</p>
)}
</div>
<button
onClick={() => handleDelete(med.id)}
className="text-red-500 p-2"
className="text-red-500 dark:text-red-400 p-2"
>
<TrashIcon size={18} />
</button>
</div>
{/* Adherence */}
<div className="mt-3 pt-3 border-t border-gray-100">
<div className="mt-3 pt-3 border-t border-gray-100 dark:border-gray-700">
{isPrn || adherencePercent === null ? (
<span className="text-sm text-gray-400">PRN &mdash; no adherence tracking</span>
<span className="text-sm text-gray-400 dark:text-gray-500">PRN &mdash; no adherence tracking</span>
) : (
<>
<div className="flex items-center justify-between mb-1">
<span className="text-sm text-gray-500">30-day adherence</span>
<span className={`font-semibold ${adherencePercent >= 80 ? 'text-green-600' : adherencePercent >= 50 ? 'text-yellow-600' : 'text-red-600'}`}>
<span className="text-sm text-gray-500 dark:text-gray-400">30-day adherence</span>
<span className={`font-semibold ${adherencePercent >= 80 ? 'text-green-600 dark:text-green-400' : adherencePercent >= 50 ? 'text-yellow-600 dark:text-yellow-400' : 'text-red-600 dark:text-red-400'}`}>
{adherencePercent}%
</span>
</div>
<div className="h-2 bg-gray-100 rounded-full overflow-hidden">
<div className="h-2 bg-gray-100 dark:bg-gray-700 rounded-full overflow-hidden">
<div
className={`h-full ${adherencePercent >= 80 ? 'bg-green-500' : adherencePercent >= 50 ? 'bg-yellow-500' : 'bg-red-500'}`}
style={{ width: `${adherencePercent}%` }}